diff options
author | Ayke van Laethem <[email protected]> | 2021-12-09 19:37:13 +0100 |
---|---|---|
committer | Nia <[email protected]> | 2021-12-28 18:29:05 -0500 |
commit | 3e109fca5f289f5e97548f061d7e0e24c9727193 (patch) | |
tree | 4ebb81cdee9d11f98cd22435a90ba0a15082f302 /builder/buildid.go | |
parent | 763a86cd8ebbc85b721cb945e7acf19d5e0b7ace (diff) | |
download | tinygo-3e109fca5f289f5e97548f061d7e0e24c9727193.tar.gz tinygo-3e109fca5f289f5e97548f061d7e0e24c9727193.zip |
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.
Diffstat (limited to 'builder/buildid.go')
-rw-r--r-- | builder/buildid.go | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/builder/buildid.go b/builder/buildid.go new file mode 100644 index 000000000..e5529c5dd --- /dev/null +++ b/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) +} |