MergeInto names the changed files that are compiled programs, read in the workspace, so a check can refuse build output (issue 1577)
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Program is a changed file whose first bytes are an executable binary: a compiled program, which is build output and not
|
||||
// source (novox/hq issue 1577: `go build` in a module's folder writes one beside go.mod, and `git add -A` takes it).
|
||||
type Program struct {
|
||||
Path string `json:"path"`
|
||||
Kind string `json:"kind"` // ELF, Mach-O or PE
|
||||
}
|
||||
|
||||
// ExecutableKind says what executable format the first bytes of a file are, or "" when they are none. A PE needs its header
|
||||
// ("PE\0\0" at the offset in bytes 0x3c) so a text starting with "MZ" is not one; a fat Mach-O (0xCAFEBABE) is told from a
|
||||
// Java class by its architecture count (bytes 4 to 7, fewer than 0x20; a class has its version there).
|
||||
func ExecutableKind(head []byte) string {
|
||||
switch {
|
||||
case bytes.HasPrefix(head, []byte("\x7fELF")):
|
||||
return "ELF"
|
||||
case bytes.HasPrefix(head, []byte("MZ")):
|
||||
if len(head) >= 0x40 {
|
||||
off := int(head[0x3c]) | int(head[0x3d])<<8 | int(head[0x3e])<<16 | int(head[0x3f])<<24
|
||||
if off >= 0 && off+4 <= len(head) && bytes.Equal(head[off:off+4], []byte("PE\x00\x00")) {
|
||||
return "PE"
|
||||
}
|
||||
}
|
||||
case bytes.HasPrefix(head, []byte{0xcf, 0xfa, 0xed, 0xfe}), bytes.HasPrefix(head, []byte{0xce, 0xfa, 0xed, 0xfe}),
|
||||
bytes.HasPrefix(head, []byte{0xfe, 0xed, 0xfa, 0xcf}), bytes.HasPrefix(head, []byte{0xfe, 0xed, 0xfa, 0xce}),
|
||||
bytes.HasPrefix(head, []byte{0xca, 0xfe, 0xba, 0xbe}) && len(head) >= 8 && head[4] == 0 && head[5] == 0 && head[6] == 0 && head[7] < 0x20:
|
||||
return "Mach-O"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// programHeadSize is how many bytes of a file are read to tell its format: enough for a PE header at its usual offsets.
|
||||
const programHeadSize = 0x100
|
||||
|
||||
// programsIn reads the output of `git diff --raw -z --no-renames --diff-filter=AM` (":<old mode> <new mode> <old sha> <new
|
||||
// sha> <status>\0<path>\0" per file) and names each added or modified regular file of the tree dir that is a compiled
|
||||
// program, from its first bytes read locally. A symlink (mode 120000) and a submodule (160000) are not read. It never
|
||||
// answers nil, so a caller can tell no programs from none reported.
|
||||
func programsIn(dir, raw string) []Program {
|
||||
out := []Program{}
|
||||
f := strings.Split(raw, "\x00")
|
||||
for i := 0; i+1 < len(f); i += 2 {
|
||||
meta := strings.Fields(strings.TrimPrefix(f[i], ":"))
|
||||
if len(meta) < 2 || (meta[1] != "100644" && meta[1] != "100755") {
|
||||
continue
|
||||
}
|
||||
p := f[i+1]
|
||||
full := filepath.Join(dir, filepath.FromSlash(p))
|
||||
if st, err := os.Lstat(full); err != nil || !st.Mode().IsRegular() {
|
||||
continue
|
||||
}
|
||||
fh, err := os.Open(full)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
head := make([]byte, programHeadSize)
|
||||
n, _ := io.ReadFull(fh, head)
|
||||
fh.Close()
|
||||
if k := ExecutableKind(head[:n]); k != "" {
|
||||
out = append(out, Program{Path: p, Kind: k})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user