builder: use build ID as cache key
Instead of storing an increasing version number in relevant packages (compiler.Version, interp.Version, cgo.Version, ...), read the build ID from the currently running executable. This has several benefits: * All changes relevant to the compiled packages are caught. * No need to bump the version for each change to these packages. This avoids merge conflicts. * During development, `go install` is enough. No need to run `tinygo clean` all the time. Of course, the drawback is that it might be updated a bit more often than necessary but I think the overall benefit is big. Regular release users shouldn't see any difference. Because the tinygo binary stays the same, the cache works well.
Этот коммит содержится в:
родитель
763a86cd8e
коммит
3e109fca5f
5 изменённых файлов: 103 добавлений и 24 удалений
|
@ -24,7 +24,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gofrs/flock"
|
"github.com/gofrs/flock"
|
||||||
"github.com/tinygo-org/tinygo/cgo"
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
"github.com/tinygo-org/tinygo/compiler"
|
"github.com/tinygo-org/tinygo/compiler"
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
|
@ -64,9 +63,8 @@ type BuildResult struct {
|
||||||
// implementation of an imported package changes.
|
// implementation of an imported package changes.
|
||||||
type packageAction struct {
|
type packageAction struct {
|
||||||
ImportPath string
|
ImportPath string
|
||||||
CGoVersion int // cgo.Version
|
CompilerBuildID string
|
||||||
CompilerVersion int // compiler.Version
|
TinyGoVersion string
|
||||||
InterpVersion int // interp.Version
|
|
||||||
LLVMVersion string
|
LLVMVersion string
|
||||||
Config *compiler.Config
|
Config *compiler.Config
|
||||||
CFlags []string
|
CFlags []string
|
||||||
|
@ -84,6 +82,13 @@ type packageAction struct {
|
||||||
// The error value may be of type *MultiError. Callers will likely want to check
|
// The error value may be of type *MultiError. Callers will likely want to check
|
||||||
// for this case and print such errors individually.
|
// for this case and print such errors individually.
|
||||||
func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
|
func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
|
||||||
|
// Read the build ID of the tinygo binary.
|
||||||
|
// Used as a cache key for package builds.
|
||||||
|
compilerBuildID, err := ReadBuildID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Create a temporary directory for intermediary files.
|
// Create a temporary directory for intermediary files.
|
||||||
dir, err := ioutil.TempDir("", "tinygo")
|
dir, err := ioutil.TempDir("", "tinygo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -192,9 +197,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
||||||
// the parameters for the build.
|
// the parameters for the build.
|
||||||
actionID := packageAction{
|
actionID := packageAction{
|
||||||
ImportPath: pkg.ImportPath,
|
ImportPath: pkg.ImportPath,
|
||||||
CGoVersion: cgo.Version,
|
CompilerBuildID: string(compilerBuildID),
|
||||||
CompilerVersion: compiler.Version,
|
TinyGoVersion: goenv.Version,
|
||||||
InterpVersion: interp.Version,
|
|
||||||
LLVMVersion: llvm.Version,
|
LLVMVersion: llvm.Version,
|
||||||
Config: compilerConfig,
|
Config: compilerConfig,
|
||||||
CFlags: pkg.CFlags,
|
CFlags: pkg.CFlags,
|
||||||
|
|
92
builder/buildid.go
Обычный файл
92
builder/buildid.go
Обычный файл
|
@ -0,0 +1,92 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"debug/elf"
|
||||||
|
"debug/macho"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadBuildID reads the build ID from the currently running executable.
|
||||||
|
func ReadBuildID() ([]byte, error) {
|
||||||
|
executable, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := os.Open(executable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux", "freebsd":
|
||||||
|
// Read the GNU build id section. (Not sure about FreeBSD though...)
|
||||||
|
file, err := elf.NewFile(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, section := range file.Sections {
|
||||||
|
if section.Type != elf.SHT_NOTE || section.Name != ".note.gnu.build-id" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf := make([]byte, section.Size)
|
||||||
|
n, err := section.ReadAt(buf, 0)
|
||||||
|
if uint64(n) != section.Size || err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read build id: %w", err)
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
case "darwin":
|
||||||
|
// Read the LC_UUID load command, which contains the equivalent of a
|
||||||
|
// build ID.
|
||||||
|
file, err := macho.NewFile(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, load := range file.Loads {
|
||||||
|
// Unfortunately, the debug/macho package doesn't support the
|
||||||
|
// LC_UUID command directly. So we have to read it from
|
||||||
|
// macho.LoadBytes.
|
||||||
|
load, ok := load.(macho.LoadBytes)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
raw := load.Raw()
|
||||||
|
command := binary.LittleEndian.Uint32(raw)
|
||||||
|
if command != 0x1b {
|
||||||
|
// Looking for the LC_UUID load command.
|
||||||
|
// LC_UUID is defined here as 0x1b:
|
||||||
|
// https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return raw[4:], nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// On other platforms (such as Windows) there isn't such a convenient
|
||||||
|
// build ID. Luckily, Go does have an equivalent of the build ID, which
|
||||||
|
// is stored as a special symbol named go.buildid. You can read it
|
||||||
|
// using `go tool buildid`, but the code below extracts it directly
|
||||||
|
// from the binary.
|
||||||
|
// Unfortunately, because of stripping with the -w flag, no symbol
|
||||||
|
// table might be available. Therefore, we have to scan the binary
|
||||||
|
// directly. Luckily the build ID is always at the start of the file.
|
||||||
|
// For details, see:
|
||||||
|
// https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go
|
||||||
|
fileStart := make([]byte, 4096)
|
||||||
|
_, err := io.ReadFull(f, fileStart)
|
||||||
|
index := bytes.Index(fileStart, []byte("\xff Go build ID: \""))
|
||||||
|
if index < 0 || index > len(fileStart)-103 {
|
||||||
|
return nil, fmt.Errorf("could not find build id in %s", err)
|
||||||
|
}
|
||||||
|
buf := fileStart[index : index+103]
|
||||||
|
if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) {
|
||||||
|
return buf[len("\xff Go build ID: \"") : len(buf)-1], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not find build ID in %s", executable)
|
||||||
|
}
|
|
@ -26,12 +26,6 @@ import (
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version of the cgo package. It must be incremented whenever the cgo package
|
|
||||||
// is changed in a way that affects the output so that cached package builds
|
|
||||||
// will be invalidated.
|
|
||||||
// This version is independent of the TinyGo version number.
|
|
||||||
const Version = 1 // last change: run libclang once per Go file
|
|
||||||
|
|
||||||
// cgoPackage holds all CGo-related information of a package.
|
// cgoPackage holds all CGo-related information of a package.
|
||||||
type cgoPackage struct {
|
type cgoPackage struct {
|
||||||
generated *ast.File
|
generated *ast.File
|
||||||
|
|
|
@ -20,11 +20,6 @@ import (
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version of the compiler pacakge. Must be incremented each time the compiler
|
|
||||||
// package changes in a way that affects the generated LLVM module.
|
|
||||||
// This version is independent of the TinyGo version number.
|
|
||||||
const Version = 25 // last change: add "target-cpu" and "target-features" attributes
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
llvm.InitializeAllTargets()
|
llvm.InitializeAllTargets()
|
||||||
llvm.InitializeAllTargetMCs()
|
llvm.InitializeAllTargetMCs()
|
||||||
|
|
|
@ -11,12 +11,6 @@ import (
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version of the interp package. It must be incremented whenever the interp
|
|
||||||
// package is changed in a way that affects the output so that cached package
|
|
||||||
// builds will be invalidated.
|
|
||||||
// This version is independent of the TinyGo version number.
|
|
||||||
const Version = 2 // last change: fix GEP on untyped pointers
|
|
||||||
|
|
||||||
// Enable extra checks, which should be disabled by default.
|
// Enable extra checks, which should be disabled by default.
|
||||||
// This may help track down bugs by adding a few more sanity checks.
|
// This may help track down bugs by adding a few more sanity checks.
|
||||||
const checks = true
|
const checks = true
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче