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  +}