@@ -6,17 +6,20 @@ package main
6
6
7
7
import (
8
8
"bytes"
9
+ "crypto/sha1"
9
10
"errors"
10
11
"fmt"
11
12
"go/build"
12
13
"go/scanner"
13
14
"go/token"
15
+ "io"
14
16
"io/ioutil"
15
17
"os"
16
18
pathpkg "path"
17
19
"path/filepath"
18
20
"runtime"
19
21
"sort"
22
+ "strconv"
20
23
"strings"
21
24
"time"
22
25
"unicode"
@@ -95,6 +98,7 @@ type Package struct {
95
98
coverMode string // preprocess Go source files with the coverage tool in this mode
96
99
coverVars map [string ]* CoverVar // variables created by coverage analysis
97
100
omitDWARF bool // tell linker not to write DWARF information
101
+ buildID string // expected build ID for generated package
98
102
}
99
103
100
104
// CoverVar holds the name of the generated coverage variables targeting the named file.
@@ -687,6 +691,36 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
687
691
}
688
692
}
689
693
694
+ // Compute build ID for this package.
695
+ // Build ID is hash of information we want to detect changes in.
696
+ // The mtime-based checks in computeStale take care of most
697
+ // of that information, but they cannot detect the removal of a
698
+ // source file from a directory (with no changes to files that remain
699
+ // and no new files in that directory). We hash the list of source
700
+ // files (without full path, to allow moving the entire tree)
701
+ // so that if one is removed, we detect it via the build IDs.
702
+ // In the future we might include other relevant information,
703
+ // like build tags or whether we're using the race detector or
704
+ // (if it becomes cheap enough) file contents.
705
+ h := sha1 .New ()
706
+ inputFiles := stringList (
707
+ p .GoFiles ,
708
+ p .CgoFiles ,
709
+ p .CFiles ,
710
+ p .CXXFiles ,
711
+ p .MFiles ,
712
+ p .HFiles ,
713
+ p .SFiles ,
714
+ p .SysoFiles ,
715
+ p .SwigFiles ,
716
+ p .SwigCXXFiles ,
717
+ )
718
+ fmt .Fprintf (h , "%d files\n " , len (inputFiles ))
719
+ for _ , file := range inputFiles {
720
+ fmt .Fprintf (h , "%s\n " , file )
721
+ }
722
+ p .buildID = fmt .Sprintf ("%x" , h .Sum (nil ))
723
+
690
724
return p
691
725
}
692
726
@@ -795,6 +829,14 @@ func isStale(p *Package, topRoot map[string]bool) bool {
795
829
}
796
830
}
797
831
832
+ // Package is stale if the expected build ID differs from the
833
+ // recorded build ID. This catches changes like a source file
834
+ // being removed from a package directory. See issue 3895.
835
+ targetBuildID , err := readBuildID (p )
836
+ if err == nil && targetBuildID != p .buildID {
837
+ return true
838
+ }
839
+
798
840
// As a courtesy to developers installing new versions of the compiler
799
841
// frequently, define that packages are stale if they are
800
842
// older than the compiler, and commands if they are older than
@@ -814,9 +856,10 @@ func isStale(p *Package, topRoot map[string]bool) bool {
814
856
}
815
857
816
858
// Have installed copy, probably built using current compilers,
817
- // and built after its imported packages. The only reason now
859
+ // built with the right set of source files,
860
+ // and built after its imported packages. The only reason now
818
861
// that we'd have to rebuild it is if the sources were newer than
819
- // the package. If a package p is not in the same tree as any
862
+ // the package. If a package p is not in the same tree as any
820
863
// package named on the command-line, assume it is up-to-date
821
864
// no matter what the modification times on the source files indicate.
822
865
// This avoids rebuilding $GOROOT packages when people are
@@ -994,3 +1037,169 @@ func hasSubdir(root, dir string) (rel string, ok bool) {
994
1037
}
995
1038
return filepath .ToSlash (dir [len (root ):]), true
996
1039
}
1040
+
1041
+ var (
1042
+ errBuildIDToolchain = fmt .Errorf ("build ID only supported in gc toolchain" )
1043
+ errBuildIDMalformed = fmt .Errorf ("malformed object file" )
1044
+ errBuildIDUnknown = fmt .Errorf ("lost build ID" )
1045
+ )
1046
+
1047
+ var (
1048
+ bangArch = []byte ("!<arch>" )
1049
+ pkgdef = []byte ("__.PKGDEF" )
1050
+ goobject = []byte ("go object " )
1051
+ buildid = []byte ("build id " )
1052
+ )
1053
+
1054
+ // readBuildID reads the build ID from an archive or binary.
1055
+ // It only supports the gc toolchain.
1056
+ // Other toolchain maintainers should adjust this function.
1057
+ func readBuildID (p * Package ) (id string , err error ) {
1058
+ if buildToolchain != (gcToolchain {}) {
1059
+ return "" , errBuildIDToolchain
1060
+ }
1061
+
1062
+ // For commands, read build ID directly from binary.
1063
+ if p .Name == "main" {
1064
+ return readBuildIDFromBinary (p )
1065
+ }
1066
+
1067
+ // Otherwise, we expect to have an archive (.a) file,
1068
+ // and we can read the build ID from the Go export data.
1069
+ if ! strings .HasSuffix (p .Target , ".a" ) {
1070
+ return "" , & os.PathError {Op : "parse" , Path : p .Target , Err : errBuildIDUnknown }
1071
+ }
1072
+
1073
+ // Read just enough of the target to fetch the build ID.
1074
+ // The archive is expected to look like:
1075
+ //
1076
+ // !<arch>
1077
+ // __.PKGDEF 0 0 0 644 7955 `
1078
+ // go object darwin amd64 devel X:none
1079
+ // build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224"
1080
+ //
1081
+ // The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none).
1082
+ // Reading the first 1024 bytes should be plenty.
1083
+ f , err := os .Open (p .Target )
1084
+ if err != nil {
1085
+ return "" , err
1086
+ }
1087
+ data := make ([]byte , 1024 )
1088
+ n , err := io .ReadFull (f , data )
1089
+ f .Close ()
1090
+
1091
+ if err != nil && n == 0 {
1092
+ return "" , err
1093
+ }
1094
+
1095
+ bad := func () (string , error ) {
1096
+ return "" , & os.PathError {Op : "parse" , Path : p .Target , Err : errBuildIDMalformed }
1097
+ }
1098
+
1099
+ // Archive header.
1100
+ for i := 0 ; ; i ++ { // returns during i==3
1101
+ j := bytes .IndexByte (data , '\n' )
1102
+ if j < 0 {
1103
+ return bad ()
1104
+ }
1105
+ line := data [:j ]
1106
+ data = data [j + 1 :]
1107
+ switch i {
1108
+ case 0 :
1109
+ if ! bytes .Equal (line , bangArch ) {
1110
+ return bad ()
1111
+ }
1112
+ case 1 :
1113
+ if ! bytes .HasPrefix (line , pkgdef ) {
1114
+ return bad ()
1115
+ }
1116
+ case 2 :
1117
+ if ! bytes .HasPrefix (line , goobject ) {
1118
+ return bad ()
1119
+ }
1120
+ case 3 :
1121
+ if ! bytes .HasPrefix (line , buildid ) {
1122
+ // Found the object header, just doesn't have a build id line.
1123
+ // Treat as successful, with empty build id.
1124
+ return "" , nil
1125
+ }
1126
+ id , err := strconv .Unquote (string (line [len (buildid ):]))
1127
+ if err != nil {
1128
+ return bad ()
1129
+ }
1130
+ return id , nil
1131
+ }
1132
+ }
1133
+ }
1134
+
1135
+ var (
1136
+ goBinary = []byte ("\x00 \n \n go binary\n " )
1137
+ endGoBinary = []byte ("\n end go binary\n " )
1138
+ newlineAndBuildid = []byte ("\n build id " )
1139
+ )
1140
+
1141
+ // readBuildIDFromBinary reads the build ID from a binary.
1142
+ // Instead of trying to be good citizens and store the build ID in a
1143
+ // custom section of the binary, which would be different for each
1144
+ // of the four binary types we support (ELF, Mach-O, Plan 9, PE),
1145
+ // we write a few lines to the end of the binary.
1146
+ //
1147
+ // At the very end of the binary we expect to find:
1148
+ //
1149
+ // <NUL>
1150
+ //
1151
+ // go binary
1152
+ // build id "XXX"
1153
+ // end go binary
1154
+ //
1155
+ func readBuildIDFromBinary (p * Package ) (id string , err error ) {
1156
+ if p .Target == "" {
1157
+ return "" , & os.PathError {Op : "parse" , Path : p .Target , Err : errBuildIDUnknown }
1158
+ }
1159
+
1160
+ f , err := os .Open (p .Target )
1161
+ if err != nil {
1162
+ return "" , err
1163
+ }
1164
+ defer f .Close ()
1165
+
1166
+ off , err := f .Seek (0 , 2 )
1167
+ if err != nil {
1168
+ return "" , err
1169
+ }
1170
+ n := 1024
1171
+ if off < int64 (n ) {
1172
+ n = int (off )
1173
+ }
1174
+ if _ , err := f .Seek (off - int64 (n ), 0 ); err != nil {
1175
+ return "" , err
1176
+ }
1177
+ data := make ([]byte , n )
1178
+ if _ , err := io .ReadFull (f , data ); err != nil {
1179
+ return "" , err
1180
+ }
1181
+ if ! bytes .HasSuffix (data , endGoBinary ) {
1182
+ // Trailer missing. Treat as successful but build ID empty.
1183
+ return "" , nil
1184
+ }
1185
+ i := bytes .LastIndex (data , goBinary )
1186
+ if i < 0 {
1187
+ // Trailer missing. Treat as successful but build ID empty.
1188
+ return "" , nil
1189
+ }
1190
+
1191
+ // Have trailer. Find build id line.
1192
+ data = data [i :]
1193
+ i = bytes .Index (data , newlineAndBuildid )
1194
+ if i < 0 {
1195
+ // Trailer present; build ID missing. Treat as successful but empty.
1196
+ return "" , nil
1197
+ }
1198
+ line := data [i + len (newlineAndBuildid ):]
1199
+ j := bytes .IndexByte (line , '\n' ) // must succeed - endGoBinary is at end and has newlines
1200
+ id , err = strconv .Unquote (string (line [:j ]))
1201
+ if err != nil {
1202
+ return "" , & os.PathError {Op : "parse" , Path : p .Target , Err : errBuildIDMalformed }
1203
+ }
1204
+ return id , nil
1205
+ }
0 commit comments