gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/rules_go_facts.patch (about) 1 commit 0a21a547f4ebf1d26d79854512cc2e0f1e1e4e90 2 Author: Andrei Vagin <avagin@google.com> 3 Date: Thu Feb 15 22:30:22 2024 -0800 4 5 Revert "Emit nogo facts into a separate archive (#3789)" 6 7 This reverts commit 30099a6add3c43706b4eec82b773b78310874935. 8 9 diff --git a/go/private/actions/archive.bzl b/go/private/actions/archive.bzl 10 index 42bf2039..a4e737ee 100644 11 --- a/go/private/actions/archive.bzl 12 +++ b/go/private/actions/archive.bzl 13 @@ -58,13 +58,9 @@ def emit_archive(go, source = None, _recompile_suffix = "", recompile_internal_d 14 pre_ext += _recompile_suffix 15 out_lib = go.declare_file(go, name = source.library.name, ext = pre_ext + ".a") 16 17 - # store export information for compiling dependent packages separately 18 + # store __.PKGDEF and nogo facts in .x 19 out_export = go.declare_file(go, name = source.library.name, ext = pre_ext + ".x") 20 out_cgo_export_h = None # set if cgo used in c-shared or c-archive mode 21 - out_facts = None 22 - nogo = go.get_nogo(go) 23 - if nogo: 24 - out_facts = go.declare_file(go, name = source.library.name, ext = pre_ext + ".facts") 25 26 direct = [get_archive(dep) for dep in source.deps] 27 runfiles = source.runfiles 28 @@ -109,8 +105,6 @@ def emit_archive(go, source = None, _recompile_suffix = "", recompile_internal_d 29 archives = direct, 30 out_lib = out_lib, 31 out_export = out_export, 32 - out_facts = out_facts, 33 - nogo = nogo, 34 out_cgo_export_h = out_cgo_export_h, 35 gc_goopts = source.gc_goopts, 36 cgo = True, 37 @@ -135,8 +129,6 @@ def emit_archive(go, source = None, _recompile_suffix = "", recompile_internal_d 38 archives = direct, 39 out_lib = out_lib, 40 out_export = out_export, 41 - out_facts = out_facts, 42 - nogo = nogo, 43 gc_goopts = source.gc_goopts, 44 cgo = False, 45 testfilter = testfilter, 46 @@ -181,7 +173,6 @@ def emit_archive(go, source = None, _recompile_suffix = "", recompile_internal_d 47 # Information needed by dependents 48 file = out_lib, 49 export_file = out_export, 50 - facts_file = out_facts, 51 data_files = as_tuple(data_files), 52 _cgo_deps = as_tuple(cgo_deps), 53 ) 54 diff --git a/go/private/actions/compilepkg.bzl b/go/private/actions/compilepkg.bzl 55 index 10fa6970..48adb910 100644 56 --- a/go/private/actions/compilepkg.bzl 57 +++ b/go/private/actions/compilepkg.bzl 58 @@ -28,18 +28,6 @@ def _archive(v): 59 v.data.export_file.path if v.data.export_file else v.data.file.path, 60 ) 61 62 -def _facts(v): 63 - facts_file = v.data.facts_file 64 - if not facts_file: 65 - return None 66 - importpaths = [v.data.importpath] 67 - importpaths.extend(v.data.importpath_aliases) 68 - return "{}={}={}".format( 69 - ":".join(importpaths), 70 - v.data.importmap, 71 - facts_file.path, 72 - ) 73 - 74 def _embedroot_arg(src): 75 return src.root.path 76 77 @@ -67,8 +55,6 @@ def emit_compilepkg( 78 clinkopts = [], 79 out_lib = None, 80 out_export = None, 81 - out_facts = None, 82 - nogo = None, 83 out_cgo_export_h = None, 84 gc_goopts = [], 85 testfilter = None, # TODO: remove when test action compiles packages 86 @@ -78,8 +64,6 @@ def emit_compilepkg( 87 fail("sources is a required parameter") 88 if out_lib == None: 89 fail("out_lib is a required parameter") 90 - if bool(nogo) != bool(out_facts): 91 - fail("nogo must be specified if and only if out_facts is specified") 92 93 inputs = (sources + embedsrcs + [go.package_list] + 94 [archive.data.export_file for archive in archives] + 95 @@ -124,13 +108,10 @@ def emit_compilepkg( 96 args.add("-p", importmap) 97 args.add("-package_list", go.package_list) 98 99 - args.add("-lo", out_lib) 100 - args.add("-o", out_export) 101 + args.add("-o", out_lib) 102 + args.add("-x", out_export) 103 + nogo = go.get_nogo(go) 104 if nogo: 105 - args.add_all(archives, before_each = "-facts", map_each = _facts) 106 - inputs.extend([archive.data.facts_file for archive in archives if archive.data.facts_file]) 107 - args.add("-out_facts", out_facts) 108 - outputs.append(out_facts) 109 args.add("-nogo", nogo) 110 inputs.append(nogo) 111 if out_cgo_export_h: 112 diff --git a/go/providers.rst b/go/providers.rst 113 index a2361ac1..dccc0e1e 100644 114 --- a/go/providers.rst 115 +++ b/go/providers.rst 116 @@ -260,15 +260,7 @@ rule. Instead, it's referenced in the ``data`` field of GoArchive_. 117 +--------------------------------+-----------------------------------------------------------------+ 118 | :param:`file` | :type:`File` | 119 +--------------------------------+-----------------------------------------------------------------+ 120 -| The archive file for the linker produced when this library is compiled. | 121 -+--------------------------------+-----------------------------------------------------------------+ 122 -| :param:`export_file` | :type:`File` | 123 -+--------------------------------+-----------------------------------------------------------------+ 124 -| The archive file for compilation of dependent libraries produced when this library is compiled. | 125 -+--------------------------------+-----------------------------------------------------------------+ 126 -| :param:`facts_file` | :type:`File` | 127 -+--------------------------------+-----------------------------------------------------------------+ 128 -| The serialized facts for this library produced when nogo ran for this library. | 129 +| The archive file produced when this library is compiled. | 130 +--------------------------------+-----------------------------------------------------------------+ 131 | :param:`srcs` | :type:`tuple of File` | 132 +--------------------------------+-----------------------------------------------------------------+ 133 diff --git a/go/tools/builders/BUILD.bazel b/go/tools/builders/BUILD.bazel 134 index d327a3af..1b44a15c 100644 135 --- a/go/tools/builders/BUILD.bazel 136 +++ b/go/tools/builders/BUILD.bazel 137 @@ -76,6 +76,7 @@ filegroup( 138 "generate_test_main.go", 139 "importcfg.go", 140 "link.go", 141 + "pack.go", 142 "read.go", 143 "replicate.go", 144 "stdlib.go", 145 @@ -96,6 +97,7 @@ go_source( 146 "nogo_typeparams_go117.go", 147 "nogo_typeparams_go118.go", 148 "nolint.go", 149 + "pack.go", 150 ], 151 # //go/tools/builders:nogo_srcs is considered a different target by 152 # Bazel's visibility check than 153 diff --git a/go/tools/builders/ar.go b/go/tools/builders/ar.go 154 index d2de6b96..2f4b36c8 100644 155 --- a/go/tools/builders/ar.go 156 +++ b/go/tools/builders/ar.go 157 @@ -23,18 +23,6 @@ import ( 158 "strings" 159 ) 160 161 -const ( 162 - // arHeader appears at the beginning of archives created by "ar" and 163 - // "go tool pack" on all platforms. 164 - arHeader = "!<arch>\n" 165 - 166 - // entryLength is the size in bytes of the metadata preceding each file 167 - // in an archive. 168 - entryLength = 60 169 -) 170 - 171 -var zeroBytes = []byte("0 ") 172 - 173 type header struct { 174 NameRaw [16]byte 175 ModTimeRaw [12]byte 176 diff --git a/go/tools/builders/cgo2.go b/go/tools/builders/cgo2.go 177 index 80043e46..fc2876a9 100644 178 --- a/go/tools/builders/cgo2.go 179 +++ b/go/tools/builders/cgo2.go 180 @@ -23,11 +23,9 @@ package main 181 import ( 182 "bytes" 183 "fmt" 184 - "io" 185 "io/ioutil" 186 "os" 187 "path/filepath" 188 - "runtime" 189 "strings" 190 ) 191 192 @@ -397,34 +395,3 @@ func (e cgoError) Error() string { 193 fmt.Fprintf(b, "Ensure that 'cgo = True' is set and the C/C++ toolchain is configured.") 194 return b.String() 195 } 196 - 197 -func copyFile(inPath, outPath string) error { 198 - inFile, err := os.Open(inPath) 199 - if err != nil { 200 - return err 201 - } 202 - defer inFile.Close() 203 - outFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) 204 - if err != nil { 205 - return err 206 - } 207 - defer outFile.Close() 208 - _, err = io.Copy(outFile, inFile) 209 - return err 210 -} 211 - 212 -func linkFile(inPath, outPath string) error { 213 - inPath, err := filepath.Abs(inPath) 214 - if err != nil { 215 - return err 216 - } 217 - return os.Symlink(inPath, outPath) 218 -} 219 - 220 -func copyOrLinkFile(inPath, outPath string) error { 221 - if runtime.GOOS == "windows" { 222 - return copyFile(inPath, outPath) 223 - } else { 224 - return linkFile(inPath, outPath) 225 - } 226 -} 227 diff --git a/go/tools/builders/compilepkg.go b/go/tools/builders/compilepkg.go 228 index b909fa86..46cae3c0 100644 229 --- a/go/tools/builders/compilepkg.go 230 +++ b/go/tools/builders/compilepkg.go 231 @@ -50,9 +50,9 @@ func compilePkg(args []string) error { 232 fs := flag.NewFlagSet("GoCompilePkg", flag.ExitOnError) 233 goenv := envFlags(fs) 234 var unfilteredSrcs, coverSrcs, embedSrcs, embedLookupDirs, embedRoots, recompileInternalDeps multiFlag 235 - var deps, facts archiveMultiFlag 236 + var deps archiveMultiFlag 237 var importPath, packagePath, nogoPath, packageListPath, coverMode string 238 - var outLinkobjPath, outInterfacePath, outFactsPath, cgoExportHPath string 239 + var outPath, outFactsPath, cgoExportHPath string 240 var testFilter string 241 var gcFlags, asmFlags, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags quoteMultiFlag 242 var coverFormat string 243 @@ -63,7 +63,6 @@ func compilePkg(args []string) error { 244 fs.Var(&embedLookupDirs, "embedlookupdir", "Root-relative paths to directories relative to which //go:embed directives are resolved") 245 fs.Var(&embedRoots, "embedroot", "Bazel output root under which a file passed via -embedsrc resides") 246 fs.Var(&deps, "arc", "Import path, package path, and file name of a direct dependency, separated by '='") 247 - fs.Var(&facts, "facts", "Import path, package path, and file name of a direct dependency's nogo facts file, separated by '='") 248 fs.StringVar(&importPath, "importpath", "", "The import path of the package being compiled. Not passed to the compiler, but may be displayed in debug data.") 249 fs.StringVar(&packagePath, "p", "", "The package path (importmap) of the package being compiled") 250 fs.Var(&gcFlags, "gcflags", "Go compiler flags") 251 @@ -77,9 +76,8 @@ func compilePkg(args []string) error { 252 fs.StringVar(&nogoPath, "nogo", "", "The nogo binary. If unset, nogo will not be run.") 253 fs.StringVar(&packageListPath, "package_list", "", "The file containing the list of standard library packages") 254 fs.StringVar(&coverMode, "cover_mode", "", "The coverage mode to use. Empty if coverage instrumentation should not be added.") 255 - fs.StringVar(&outLinkobjPath, "lo", "", "The full output archive file required by the linker") 256 - fs.StringVar(&outInterfacePath, "o", "", "The export-only output archive required to compile dependent packages") 257 - fs.StringVar(&outFactsPath, "out_facts", "", "The file to emit serialized nogo facts to (must be set if -nogo is set") 258 + fs.StringVar(&outPath, "o", "", "The output archive file to write compiled code") 259 + fs.StringVar(&outFactsPath, "x", "", "The output archive file to write export data and nogo facts") 260 fs.StringVar(&cgoExportHPath, "cgoexport", "", "The _cgo_exports.h file to write") 261 fs.StringVar(&testFilter, "testfilter", "off", "Controls test package filtering") 262 fs.StringVar(&coverFormat, "cover_format", "", "Emit source file paths in coverage instrumentation suitable for the specified coverage format") 263 @@ -96,7 +94,7 @@ func compilePkg(args []string) error { 264 } 265 cgoEnabled := os.Getenv("CGO_ENABLED") == "1" 266 cc := os.Getenv("CC") 267 - outLinkobjPath = abs(outLinkobjPath) 268 + outPath = abs(outPath) 269 for i := range unfilteredSrcs { 270 unfilteredSrcs[i] = abs(unfilteredSrcs[i]) 271 } 272 @@ -144,7 +142,6 @@ func compilePkg(args []string) error { 273 packagePath, 274 srcs, 275 deps, 276 - facts, 277 coverMode, 278 coverSrcs, 279 embedSrcs, 280 @@ -162,8 +159,7 @@ func compilePkg(args []string) error { 281 ldFlags, 282 nogoPath, 283 packageListPath, 284 - outLinkobjPath, 285 - outInterfacePath, 286 + outPath, 287 outFactsPath, 288 cgoExportHPath, 289 coverFormat, 290 @@ -177,7 +173,6 @@ func compileArchive( 291 packagePath string, 292 srcs archiveSrcs, 293 deps []archive, 294 - facts []archive, 295 coverMode string, 296 coverSrcs []string, 297 embedSrcs []string, 298 @@ -195,9 +190,8 @@ func compileArchive( 299 ldFlags []string, 300 nogoPath string, 301 packageListPath string, 302 - outLinkObj string, 303 - outInterfacePath string, 304 - outFactsPath string, 305 + outPath string, 306 + outXPath string, 307 cgoExportHPath string, 308 coverFormat string, 309 recompileInternalDeps []string, 310 @@ -215,7 +209,7 @@ func compileArchive( 311 // Otherwise, GoPack will complain if we try to add assembly or cgo objects. 312 // A truly empty archive does not include any references to source file paths, which 313 // ensures hermeticity even though the temp file path is random. 314 - emptyGoFile, err := os.CreateTemp(filepath.Dir(outLinkObj), "*.go") 315 + emptyGoFile, err := os.CreateTemp(filepath.Dir(outPath), "*.go") 316 if err != nil { 317 return err 318 } 319 @@ -406,7 +400,7 @@ func compileArchive( 320 } 321 322 // Build an importcfg file for the compiler. 323 - importcfgPath, err := buildImportcfgFileForCompile(imports, goenv.installSuffix, filepath.Dir(outLinkObj)) 324 + importcfgPath, err := buildImportcfgFileForCompile(imports, goenv.installSuffix, filepath.Dir(outPath)) 325 if err != nil { 326 return err 327 } 328 @@ -449,11 +443,12 @@ func compileArchive( 329 330 // Run nogo concurrently. 331 var nogoChan chan error 332 - if nogoPath != "" { 333 + outFactsPath := filepath.Join(workDir, nogoFact) 334 + if nogoPath != "" && len(goSrcsNogo) > 0 { 335 ctx, cancel := context.WithCancel(context.Background()) 336 nogoChan = make(chan error) 337 go func() { 338 - nogoChan <- runNogo(ctx, workDir, nogoPath, goSrcsNogo, facts, packagePath, importcfgPath, outFactsPath) 339 + nogoChan <- runNogo(ctx, workDir, nogoPath, goSrcsNogo, deps, packagePath, importcfgPath, outFactsPath) 340 }() 341 defer func() { 342 if nogoChan != nil { 343 @@ -483,7 +478,7 @@ func compileArchive( 344 } 345 346 // Compile the filtered .go files. 347 - if err := compileGo(goenv, goSrcs, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath, gcFlags, pgoprofile, outLinkObj, outInterfacePath); err != nil { 348 + if err := compileGo(goenv, goSrcs, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath, gcFlags, pgoprofile, outPath); err != nil { 349 return err 350 } 351 352 @@ -517,25 +512,44 @@ func compileArchive( 353 // Pack .o files into the archive. These may come from cgo generated code, 354 // cgo dependencies (cdeps), or assembly. 355 if len(objFiles) > 0 { 356 - if err := appendToArchive(goenv, outLinkObj, objFiles); err != nil { 357 + if err := appendFiles(goenv, outPath, objFiles); err != nil { 358 return err 359 } 360 } 361 362 // Check results from nogo. 363 + nogoStatus := nogoNotRun 364 if nogoChan != nil { 365 err := <-nogoChan 366 nogoChan = nil // no cancellation needed 367 if err != nil { 368 - // TODO: Move nogo into a separate action so we don't fail the compilation here. 369 + nogoStatus = nogoFailed 370 + // TODO: should we still create the .x file without nogo facts in this case? 371 return err 372 } 373 + nogoStatus = nogoSucceeded 374 + } 375 + 376 + // Extract the export data file and pack it in an .x archive together with the 377 + // nogo facts file (if there is one). This allows compile actions to depend 378 + // on .x files only, so we don't need to recompile a package when one of its 379 + // imports changes in a way that doesn't affect export data. 380 + // TODO(golang/go#33820): After Go 1.16 is the minimum supported version, 381 + // use -linkobj to tell the compiler to create separate .a and .x files for 382 + // compiled code and export data. Before that version, the linker needed 383 + // export data in the .a file when building a plugin. To work around that, 384 + // we copy the export data into .x ourselves. 385 + if err = extractFileFromArchive(outPath, workDir, pkgDef); err != nil { 386 + return err 387 } 388 - 389 - return nil 390 + pkgDefPath := filepath.Join(workDir, pkgDef) 391 + if nogoStatus == nogoSucceeded { 392 + return appendFiles(goenv, outXPath, []string{pkgDefPath, outFactsPath}) 393 + } 394 + return appendFiles(goenv, outXPath, []string{pkgDefPath}) 395 } 396 397 -func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath string, gcFlags []string, pgoprofile, outLinkobjPath, outInterfacePath string) error { 398 +func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPath, asmHdrPath, symabisPath string, gcFlags []string, pgoprofile string, outPath string) error { 399 args := goenv.goTool("compile") 400 args = append(args, "-p", packagePath, "-importcfg", importcfgPath, "-pack") 401 if embedcfgPath != "" { 402 @@ -551,24 +565,19 @@ func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPa 403 args = append(args, "-pgoprofile", pgoprofile) 404 } 405 args = append(args, gcFlags...) 406 - args = append(args, "-o", outInterfacePath) 407 - args = append(args, "-linkobj", outLinkobjPath) 408 + args = append(args, "-o", outPath) 409 args = append(args, "--") 410 args = append(args, srcs...) 411 absArgs(args, []string{"-I", "-o", "-trimpath", "-importcfg"}) 412 return goenv.runCommand(args) 413 } 414 415 -func runNogo(ctx context.Context, workDir string, nogoPath string, srcs []string, facts []archive, packagePath, importcfgPath, outFactsPath string) error { 416 - if len(srcs) == 0 { 417 - // emit_compilepkg expects a nogo facts file, even if it's empty. 418 - return os.WriteFile(outFactsPath, nil, 0o666) 419 - } 420 +func runNogo(ctx context.Context, workDir string, nogoPath string, srcs []string, deps []archive, packagePath, importcfgPath, outFactsPath string) error { 421 args := []string{nogoPath} 422 args = append(args, "-p", packagePath) 423 args = append(args, "-importcfg", importcfgPath) 424 - for _, fact := range facts { 425 - args = append(args, "-fact", fmt.Sprintf("%s=%s", fact.importPath, fact.file)) 426 + for _, dep := range deps { 427 + args = append(args, "-fact", fmt.Sprintf("%s=%s", dep.importPath, dep.file)) 428 } 429 args = append(args, "-x", outFactsPath) 430 args = append(args, srcs...) 431 @@ -598,13 +607,6 @@ func runNogo(ctx context.Context, workDir string, nogoPath string, srcs []string 432 return nil 433 } 434 435 -func appendToArchive(goenv *env, outPath string, objFiles []string) error { 436 - // Use abs to work around long path issues on Windows. 437 - args := goenv.goTool("pack", "r", abs(outPath)) 438 - args = append(args, objFiles...) 439 - return goenv.runCommand(args) 440 -} 441 - 442 func createTrimPath(gcFlags []string, path string) string { 443 for _, flag := range gcFlags { 444 if strings.HasPrefix(flag, "-trimpath=") { 445 diff --git a/go/tools/builders/nogo_main.go b/go/tools/builders/nogo_main.go 446 index 23acdef0..17ff5314 100644 447 --- a/go/tools/builders/nogo_main.go 448 +++ b/go/tools/builders/nogo_main.go 449 @@ -610,8 +610,8 @@ func (i *importer) Import(path string) (*types.Package, error) { 450 } 451 452 func (i *importer) readFacts(pkgPath string) ([]byte, error) { 453 - facts := i.factMap[pkgPath] 454 - if facts == "" { 455 + archive := i.factMap[pkgPath] 456 + if archive == "" { 457 // Packages that were not built with the nogo toolchain will not be 458 // analyzed, so there's no opportunity to store facts. This includes 459 // packages in the standard library and packages built with go_tool_library, 460 @@ -621,7 +621,18 @@ func (i *importer) readFacts(pkgPath string) ([]byte, error) { 461 // fmt.Printf accepts a format string. 462 return nil, nil 463 } 464 - return os.ReadFile(facts) 465 + factReader, err := readFileInArchive(nogoFact, archive) 466 + if os.IsNotExist(err) { 467 + // Packages that were not built with the nogo toolchain will not be 468 + // analyzed, so there's no opportunity to store facts. This includes 469 + // packages in the standard library and packages built with go_tool_library, 470 + // such as coverdata. 471 + return nil, nil 472 + } else if err != nil { 473 + return nil, err 474 + } 475 + defer factReader.Close() 476 + return ioutil.ReadAll(factReader) 477 } 478 479 type factMultiFlag map[string]string 480 diff --git a/go/tools/builders/pack.go b/go/tools/builders/pack.go 481 new file mode 100644 482 index 00000000..ddbb1930 483 --- /dev/null 484 +++ b/go/tools/builders/pack.go 485 @@ -0,0 +1,388 @@ 486 +// Copyright 2017 The Bazel Authors. All rights reserved. 487 +// 488 +// Licensed under the Apache License, Version 2.0 (the "License"); 489 +// you may not use this file except in compliance with the License. 490 +// You may obtain a copy of the License at 491 +// 492 +// http://www.apache.org/licenses/LICENSE-2.0 493 +// 494 +// Unless required by applicable law or agreed to in writing, software 495 +// distributed under the License is distributed on an "AS IS" BASIS, 496 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 497 +// See the License for the specific language governing permissions and 498 +// limitations under the License. 499 + 500 +package main 501 + 502 +import ( 503 + "bufio" 504 + "bytes" 505 + "errors" 506 + "fmt" 507 + "io" 508 + "io/ioutil" 509 + "os" 510 + "path/filepath" 511 + "runtime" 512 + "strconv" 513 + "strings" 514 +) 515 + 516 +func copyFile(inPath, outPath string) error { 517 + inFile, err := os.Open(inPath) 518 + if err != nil { 519 + return err 520 + } 521 + defer inFile.Close() 522 + outFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) 523 + if err != nil { 524 + return err 525 + } 526 + defer outFile.Close() 527 + _, err = io.Copy(outFile, inFile) 528 + return err 529 +} 530 + 531 +func linkFile(inPath, outPath string) error { 532 + inPath, err := filepath.Abs(inPath) 533 + if err != nil { 534 + return err 535 + } 536 + return os.Symlink(inPath, outPath) 537 +} 538 + 539 +func copyOrLinkFile(inPath, outPath string) error { 540 + if runtime.GOOS == "windows" { 541 + return copyFile(inPath, outPath) 542 + } else { 543 + return linkFile(inPath, outPath) 544 + } 545 +} 546 + 547 +const ( 548 + // arHeader appears at the beginning of archives created by "ar" and 549 + // "go tool pack" on all platforms. 550 + arHeader = "!<arch>\n" 551 + 552 + // entryLength is the size in bytes of the metadata preceding each file 553 + // in an archive. 554 + entryLength = 60 555 + 556 + // pkgDef is the name of the export data file within an archive 557 + pkgDef = "__.PKGDEF" 558 + 559 + // nogoFact is the name of the nogo fact file 560 + nogoFact = "nogo.out" 561 +) 562 + 563 +var zeroBytes = []byte("0 ") 564 + 565 +type bufioReaderWithCloser struct { 566 + // bufio.Reader is needed to skip bytes in archives 567 + *bufio.Reader 568 + io.Closer 569 +} 570 + 571 +func extractFiles(archive, dir string, names map[string]struct{}) (files []string, err error) { 572 + rc, err := openArchive(archive) 573 + if err != nil { 574 + return nil, err 575 + } 576 + defer rc.Close() 577 + 578 + var nameData []byte 579 + bufReader := rc.Reader 580 + for { 581 + name, size, err := readMetadata(bufReader, &nameData) 582 + if err == io.EOF { 583 + return files, nil 584 + } 585 + if err != nil { 586 + return nil, err 587 + } 588 + if !isObjectFile(name) { 589 + if err := skipFile(bufReader, size); err != nil { 590 + return nil, err 591 + } 592 + continue 593 + } 594 + name, err = simpleName(name, names) 595 + if err != nil { 596 + return nil, err 597 + } 598 + name = filepath.Join(dir, name) 599 + if err := extractFile(bufReader, name, size); err != nil { 600 + return nil, err 601 + } 602 + files = append(files, name) 603 + } 604 +} 605 + 606 +func openArchive(archive string) (bufioReaderWithCloser, error) { 607 + f, err := os.Open(archive) 608 + if err != nil { 609 + return bufioReaderWithCloser{}, err 610 + } 611 + r := bufio.NewReader(f) 612 + header := make([]byte, len(arHeader)) 613 + if _, err := io.ReadFull(r, header); err != nil || string(header) != arHeader { 614 + f.Close() 615 + return bufioReaderWithCloser{}, fmt.Errorf("%s: bad header", archive) 616 + } 617 + return bufioReaderWithCloser{r, f}, nil 618 +} 619 + 620 +// readMetadata reads the relevant fields of an entry. Before calling, 621 +// r must be positioned at the beginning of an entry. Afterward, r will 622 +// be positioned at the beginning of the file data. io.EOF is returned if 623 +// there are no more files in the archive. 624 +// 625 +// Both BSD and GNU / SysV naming conventions are supported. 626 +func readMetadata(r *bufio.Reader, nameData *[]byte) (name string, size int64, err error) { 627 +retry: 628 + // Each file is preceded by a 60-byte header that contains its metadata. 629 + // We only care about two fields, name and size. Other fields (mtime, 630 + // owner, group, mode) are ignored because they don't affect compilation. 631 + var entry [entryLength]byte 632 + if _, err := io.ReadFull(r, entry[:]); err != nil { 633 + return "", 0, err 634 + } 635 + 636 + sizeField := strings.TrimSpace(string(entry[48:58])) 637 + size, err = strconv.ParseInt(sizeField, 10, 64) 638 + if err != nil { 639 + return "", 0, err 640 + } 641 + 642 + nameField := strings.TrimRight(string(entry[:16]), " ") 643 + switch { 644 + case strings.HasPrefix(nameField, "#1/"): 645 + // BSD-style name. The number of bytes in the name is written here in 646 + // ASCII, right-padded with spaces. The actual name is stored at the 647 + // beginning of the file data, left-padded with NUL bytes. 648 + nameField = nameField[len("#1/"):] 649 + nameLen, err := strconv.ParseInt(nameField, 10, 64) 650 + if err != nil { 651 + return "", 0, err 652 + } 653 + nameBuf := make([]byte, nameLen) 654 + if _, err := io.ReadFull(r, nameBuf); err != nil { 655 + return "", 0, err 656 + } 657 + name = strings.TrimRight(string(nameBuf), "\x00") 658 + size -= nameLen 659 + 660 + case nameField == "//": 661 + // GNU / SysV-style name data. This is a fake file that contains names 662 + // for files with long names. We read this into nameData, then read 663 + // the next entry. 664 + *nameData = make([]byte, size) 665 + if _, err := io.ReadFull(r, *nameData); err != nil { 666 + return "", 0, err 667 + } 668 + if size%2 != 0 { 669 + // Files are aligned at 2-byte offsets. Discard the padding byte if the 670 + // size was odd. 671 + if _, err := r.ReadByte(); err != nil { 672 + return "", 0, err 673 + } 674 + } 675 + goto retry 676 + 677 + case nameField == "/": 678 + // GNU / SysV-style symbol lookup table. Skip. 679 + if err := skipFile(r, size); err != nil { 680 + return "", 0, err 681 + } 682 + goto retry 683 + 684 + case strings.HasPrefix(nameField, "/"): 685 + // GNU / SysV-style long file name. The number that follows the slash is 686 + // an offset into the name data that should have been read earlier. 687 + // The file name ends with a slash. 688 + nameField = nameField[1:] 689 + nameOffset, err := strconv.Atoi(nameField) 690 + if err != nil { 691 + return "", 0, err 692 + } 693 + if nameData == nil || nameOffset < 0 || nameOffset >= len(*nameData) { 694 + return "", 0, fmt.Errorf("invalid name length: %d", nameOffset) 695 + } 696 + i := bytes.IndexByte((*nameData)[nameOffset:], '/') 697 + if i < 0 { 698 + return "", 0, errors.New("file name does not end with '/'") 699 + } 700 + name = string((*nameData)[nameOffset : nameOffset+i]) 701 + 702 + case strings.HasSuffix(nameField, "/"): 703 + // GNU / SysV-style short file name. 704 + name = nameField[:len(nameField)-1] 705 + 706 + default: 707 + // Common format name. 708 + name = nameField 709 + } 710 + 711 + return name, size, err 712 +} 713 + 714 +// extractFile reads size bytes from r and writes them to a new file, name. 715 +func extractFile(r *bufio.Reader, name string, size int64) error { 716 + w, err := os.Create(name) 717 + if err != nil { 718 + return err 719 + } 720 + defer w.Close() 721 + _, err = io.CopyN(w, r, size) 722 + if err != nil { 723 + return err 724 + } 725 + if size%2 != 0 { 726 + // Files are aligned at 2-byte offsets. Discard the padding byte if the 727 + // size was odd. 728 + if _, err := r.ReadByte(); err != nil { 729 + return err 730 + } 731 + } 732 + return nil 733 +} 734 + 735 +func skipFile(r *bufio.Reader, size int64) error { 736 + if size%2 != 0 { 737 + // Files are aligned at 2-byte offsets. Discard the padding byte if the 738 + // size was odd. 739 + size += 1 740 + } 741 + _, err := r.Discard(int(size)) 742 + return err 743 +} 744 + 745 +func isObjectFile(name string) bool { 746 + return strings.HasSuffix(name, ".o") 747 +} 748 + 749 +// simpleName returns a file name which is at most 15 characters 750 +// and doesn't conflict with other names. If it is not possible to choose 751 +// such a name, simpleName will truncate the given name to 15 characters. 752 +// The original file extension will be preserved. 753 +func simpleName(name string, names map[string]struct{}) (string, error) { 754 + if _, ok := names[name]; !ok && len(name) < 16 { 755 + names[name] = struct{}{} 756 + return name, nil 757 + } 758 + var stem, ext string 759 + if i := strings.LastIndexByte(name, '.'); i < 0 { 760 + stem = name 761 + } else { 762 + stem = strings.Replace(name[:i], ".", "_", -1) 763 + ext = name[i:] 764 + } 765 + for n := 0; n < len(names)+1; n++ { 766 + ns := strconv.Itoa(n) 767 + stemLen := 15 - len(ext) - len(ns) 768 + if stemLen < 0 { 769 + break 770 + } 771 + if stemLen > len(stem) { 772 + stemLen = len(stem) 773 + } 774 + candidate := stem[:stemLen] + ns + ext 775 + if _, ok := names[candidate]; !ok { 776 + names[candidate] = struct{}{} 777 + return candidate, nil 778 + } 779 + } 780 + return "", fmt.Errorf("cannot shorten file name: %q", name) 781 +} 782 + 783 +func appendFiles(goenv *env, archive string, files []string) error { 784 + archive = abs(archive) // required for long filenames on Windows. 785 + 786 + // Create an empty archive if one doesn't already exist. 787 + // In Go 1.16, 'go tool pack r' reports an error if the archive doesn't exist. 788 + // 'go tool pack c' copies export data in addition to creating the archive, 789 + // so we don't want to use that directly. 790 + _, err := os.Stat(archive) 791 + if err != nil && !os.IsNotExist(err) { 792 + return err 793 + } 794 + if os.IsNotExist(err) { 795 + if err := ioutil.WriteFile(archive, []byte(arHeader), 0666); err != nil { 796 + return err 797 + } 798 + } 799 + 800 + // Append files to the archive. 801 + // TODO(jayconrod): copy cmd/internal/archive and use that instead of 802 + // shelling out to cmd/pack. 803 + args := goenv.goTool("pack", "r", archive) 804 + args = append(args, files...) 805 + return goenv.runCommand(args) 806 +} 807 + 808 +type readWithCloser struct { 809 + io.Reader 810 + io.Closer 811 +} 812 + 813 +func readFileInArchive(fileName, archive string) (io.ReadCloser, error) { 814 + rc, err := openArchive(archive) 815 + if err != nil { 816 + return nil, err 817 + } 818 + var nameData []byte 819 + bufReader := rc.Reader 820 + for err == nil { 821 + // avoid shadowing err in the loop it can be returned correctly in the end 822 + var ( 823 + name string 824 + size int64 825 + ) 826 + name, size, err = readMetadata(bufReader, &nameData) 827 + if err != nil { 828 + break 829 + } 830 + if name == fileName { 831 + return readWithCloser{ 832 + Reader: io.LimitReader(rc, size), 833 + Closer: rc, 834 + }, nil 835 + } 836 + err = skipFile(bufReader, size) 837 + } 838 + if err == io.EOF { 839 + err = os.ErrNotExist 840 + } 841 + rc.Close() 842 + return nil, err 843 +} 844 + 845 +func extractFileFromArchive(archive, dir, name string) (err error) { 846 + archiveReader, err := readFileInArchive(name, archive) 847 + if err != nil { 848 + return fmt.Errorf("error reading %s from %s: %v", name, archive, err) 849 + } 850 + defer func() { 851 + e := archiveReader.Close() 852 + if e != nil && err == nil { 853 + err = fmt.Errorf("error closing %q: %v", archive, e) 854 + } 855 + }() 856 + outPath := filepath.Join(dir, pkgDef) 857 + outFile, err := os.Create(outPath) 858 + if err != nil { 859 + return fmt.Errorf("error creating %s: %v", outPath, err) 860 + } 861 + defer func() { 862 + e := outFile.Close() 863 + if e != nil && err == nil { 864 + err = fmt.Errorf("error closing %q: %v", outPath, e) 865 + } 866 + }() 867 + if size, err := io.Copy(outFile, archiveReader); err != nil { 868 + return fmt.Errorf("error writing %s: %v", outPath, err) 869 + } else if size == 0 { 870 + return fmt.Errorf("%s is empty in %s", name, archive) 871 + } 872 + return err 873 +}