github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/parse/rules/go_rules.build_defs (about)

     1  """ Rules to build Go code.
     2  
     3  Go has a strong built-in concept of packages so it's probably a good idea to match Please
     4  rules to Go packages.
     5  """
     6  
     7  _GOPATH = ' '.join(['-I %s -I %s/pkg/%s_%s' % (p, p, CONFIG.OS, CONFIG.ARCH) for p in CONFIG.GOPATH.split(':')])
     8  # This links all the .a files up one level. This is necessary for some Go tools to find them.
     9  _LINK_PKGS_CMD = ' '.join([
    10      'for FN in `find . -name "*.a" | sort`; do ',
    11      'DN=${FN%/*}; BN=${FN##*/}; if [ "${DN##*/}" == "${BN%.a}" ]; then ',
    12      'ln -s $TMP_DIR/$FN ${DN%/*}; fi; done'
    13  ])
    14  
    15  
    16  def go_library(name:str, srcs:list, asm_srcs:list=None, hdrs:list=None, out:str=None, deps:list=[],
    17                 visibility:list=None, test_only:bool&testonly=False, go_tools:list=None, complete:bool=True,
    18                 _needs_transitive_deps=False, _all_srcs=False, cover:bool=True, filter_srcs:bool=True):
    19      """Generates a Go library which can be reused by other rules.
    20  
    21      Args:
    22        name (str): Name of the rule.
    23        srcs (list): Go source files to compile.
    24        asm_srcs (list): Source files to assemble with `go tool assemble`.
    25        hdrs (list): Header files needed for assembly. Has no effect if asm_srcs is not given.
    26        out (str): Name of the output library to compile (defaults to name suffixed with .a)
    27        deps (list): Dependencies
    28        visibility (list): Visibility specification
    29        test_only (bool): If True, is only visible to test rules.
    30        go_tools (list): A list of targets to pre-process your src files with go generate.
    31        complete (bool): Indicates whether the library is complete or not (ie. buildable with
    32                         `go tool build -complete`). In nearly all cases this is True (the main
    33                         exception being for cgo).
    34        cover (bool): Indicates whether this library should be considered for coverage annotations.
    35                      Libraries are only annotated when using `plz cover` (or `plz build -c cover`),
    36                      but if this is false they never will be. Can be useful for e.g. third-party
    37                      code that you never want to be instrumented.
    38        filter_srcs (bool): If True, filters source files through Go's standard build constraints.
    39      """
    40      if asm_srcs:
    41          lib_rule = go_library(
    42              name = '_%s#lib' % name,
    43              srcs = srcs,
    44              deps = deps,
    45              test_only = test_only,
    46              go_tools = go_tools,
    47              complete = False,
    48              cover = cover,
    49              _needs_transitive_deps = _needs_transitive_deps,
    50              _all_srcs = _all_srcs,
    51          )
    52          asm_rule = build_rule(
    53              name = name,
    54              tag = 'asm',
    55              srcs = {
    56                  'asm': asm_srcs,
    57                  'hdrs': hdrs,
    58              },
    59              outs = [name + '.o'],
    60              building_description = 'Assembling...',
    61              cmd = 'eval `$TOOL env` && $TOOL tool asm -trimpath $TMP_DIR -I ${GOROOT}/pkg/include -D GOOS_${OS} -D GOARCH_${ARCH} -o $OUT $SRCS_ASM',
    62              tools=[CONFIG.GO_TOOL],
    63              test_only = test_only,
    64          )
    65          return build_rule(
    66              name = name,
    67              srcs = {
    68                  'lib': [lib_rule],
    69                  'asm': [asm_rule],
    70              },
    71              outs=[out or name + '.a'],
    72              tools=[CONFIG.GO_TOOL],
    73              cmd = 'cp $SRCS_LIB $OUT && chmod +w $OUT && $TOOL tool pack r $OUT $SRCS_ASM',
    74              visibility = visibility,
    75              building_description = 'Packing...',
    76              requires = ['go'],
    77              provides = {'go': ':' + name, 'go_src': lib_rule},
    78              test_only = test_only,
    79          )
    80  
    81      # go_test and cgo_library need access to the sources as well.
    82      src_rule = filegroup(
    83          name = name,
    84          tag = 'srcs',
    85          srcs=srcs,
    86          exported_deps=deps,
    87          visibility=visibility,
    88          requires=['go'],
    89          test_only=test_only,
    90      )
    91  
    92      # Run go generate if needed.
    93      if go_tools:
    94          gen_rule = go_generate(
    95              name='_%s#gen' % name,
    96              srcs=srcs,
    97              tools=go_tools,
    98              deps=deps + [src_rule],
    99              test_only=test_only,
   100          )
   101          srcs += [gen_rule]
   102  
   103      tools = { 'go': [CONFIG.GO_TOOL] }
   104      if filter_srcs:
   105          tools['filter'] = [CONFIG.GO_FILTER_TOOL]
   106  
   107      return build_rule(
   108          name=name,
   109          srcs=srcs,
   110          deps=deps + [src_rule],
   111          outs=[out or name + '.a'],
   112          cmd=_go_library_cmds(complete=complete, all_srcs=_all_srcs, cover=cover, filter_srcs=filter_srcs),
   113          visibility=visibility,
   114          building_description="Compiling...",
   115          requires=['go', 'go_src'] if _all_srcs else ['go'],
   116          provides={'go': ':' + name, 'go_src': src_rule},
   117          test_only=test_only,
   118          tools=tools,
   119          needs_transitive_deps=_needs_transitive_deps,
   120      )
   121  
   122  
   123  def go_generate(name:str, srcs:list, tools:list, deps:list=None, visibility:list=None, test_only:bool&testonly=False):
   124      """Generates a `go generate` rule.
   125  
   126      Deprecated, use https://github.com/thought-machine/pleasings/blob/master/go/go_generate.build_defs instead.
   127      This will probably be removed in plz v13+.
   128  
   129      Args:
   130        name (str): Name of the rule.
   131        srcs (list): Go source files to run go generate over.
   132        tools (list): A list of targets which represent binaries to be used via `go generate`.
   133        deps (list): Dependencies
   134        visibility (list): Visibility specification
   135        test_only (bool): If True, is only visible to test rules.
   136      """
   137      log.warning('The builtin go_generate is deprecated, see https://github.com/thought-machine/pleasings/blob/master/go/go_generate.build_defs for an alternative.')
   138  
   139      # We simply capture all go files produced by go generate.
   140      def _post_build(rule_name, output):
   141          for out in output:
   142              if out.endswith('.go') and srcs and out not in srcs:
   143                  add_out(rule_name, out)
   144  
   145      # All the tools must be in the $PATH.
   146      cmd = ' && '.join([
   147          # It's essential that we copy all .a files up a directory as well; we tend to output them one level
   148          # down from where Go expects them to be.
   149          _LINK_PKGS_CMD,
   150          # It's also essential that the compiled .a files are under this prefix, otherwise gcimporter won't find them.
   151          'mkdir pkg',
   152          'ln -s . src',
   153          'ln -s $TMP_DIR pkg/%s_%s' % (CONFIG.OS, CONFIG.ARCH),
   154          'export PATH="$(echo "$TOOLS_GEN " | sed -E -e \'s|/[^/]+[ ]|:|g\')$PATH"',
   155          'GOPATH="%s" $TOOLS_GO generate $SRCS' % CONFIG.GOPATH,
   156          'mv $PKG_DIR/*.go .',
   157          'ls *.go'
   158      ])
   159      return build_rule(
   160          name=name,
   161          srcs=srcs,
   162          deps=deps,
   163          tools={
   164              'go': [CONFIG.GO_TOOL],
   165              'gen': tools,
   166          },
   167          cmd=cmd,
   168          visibility=visibility,
   169          test_only=test_only,
   170          post_build=_post_build,
   171          requires = ['go', 'go_src'],
   172      )
   173  
   174  
   175  def cgo_library(name:str, srcs:list, go_srcs:list=[], c_srcs:list=[], hdrs:list=[],
   176                  out:str=None, compiler_flags:list&cflags=[], linker_flags:list&ldflags=[], pkg_config:list=[],
   177                  subdir:str='', deps:list=[], visibility:list=None, test_only:bool&testonly=False):
   178      """Generates a Go library which can be reused by other rules.
   179  
   180      Note that by its nature this is something of a hybrid of Go and C rules. It can depend
   181      on C / C++ rules, given the limitations of cgo (i.e. you will have to interact with them
   182      through a C interface, although the objects themselves can contain C++). As mentioned
   183      below, you will likely be better off wrapping your dependencies into a cc_static_library
   184      rule and depending on that rather than depending directly on cc_library rules.
   185  
   186      Note also that this does not honour Go's syntactic comments; you have to explicitly
   187      specify which Go files are cgo vs. which are not, as well as C headers & sources and
   188      any required cflags or ldflags.
   189  
   190      Args:
   191        name (str): Name of the rule.
   192        srcs (list): Go source files to compile that have 'import "C"' declarations in them.
   193        go_srcs (list): Any Go source files that do *not* have 'import "C"' declarations.
   194        c_srcs (list): Any C source files to include.
   195        hdrs (list): Any C header files to include.
   196        out (str): Name of output file. Defaults to name + '.a'.
   197        compiler_flags (list): List of compiler flags to be passed when compiling the C code.
   198        linker_flags (list): List of linker flags to be passed when linking a Go binary.
   199        pkg_config (list): List of packages to pass to pkg-config.
   200        subdir (str): Subdirectory that source files are in. Required if they're not in the
   201                      current directory.
   202        deps (list): Dependencies. Note that if you intend to depend on cc_library rules,
   203                     you will likely be better off wrapping them into a cc_static_library
   204                     and depending on that.
   205        visibility (list): Visibility specification
   206        test_only (bool): If True, is only visible to test rules.
   207      """
   208      file_srcs = [src for src in srcs if not src.startswith('/') and not src.startswith(':')]
   209      post_build = lambda rule, output: [add_out(rule, 'c' if line.endswith('.c') else 'go', line) for line in output]
   210      subdir2 = (subdir + '/') if subdir and not subdir.endswith('/') else subdir
   211  
   212      cgo_rule = build_rule(
   213          name = name,
   214          tag = 'cgo',
   215          srcs = srcs + hdrs,
   216          outs = {
   217              'go': [subdir2 + src.replace('.go', '.cgo1.go') for src in file_srcs] + [subdir2 + '_cgo_gotypes.go'],
   218              'c': [subdir2 + src.replace('.go', '.cgo2.c') for src in file_srcs] + [subdir2 + '_cgo_export.c'],
   219              'h': [subdir2 + '_cgo_export.h'],
   220          },
   221          cmd = ' && '.join([
   222              ('OUT_DIR="$TMP_DIR/%s"' % subdir) if subdir else 'OUT_DIR="$TMP_DIR"',
   223              'mkdir -p $OUT_DIR',
   224              'cd $PKG_DIR/' + subdir,
   225              '$TOOL tool cgo -objdir $OUT_DIR -importpath ${PKG#*src/} *.go',
   226              # Remove the .o file which BSD sed gets upset about in the next command
   227              'rm -f $OUT_DIR/_cgo_.o $OUT_DIR/_cgo_main.c',
   228              # cgo leaves absolute paths in these files which we must get rid of :(
   229              'find $OUT_DIR -type f -maxdepth 1 | xargs sed -i -e "s|$TMP_DIR/||g"',
   230              'cd $TMP_DIR',
   231              'ls %s*.c %s*.go' % (subdir2, subdir2),
   232          ]),
   233          tools = [CONFIG.GO_TOOL],
   234          post_build = post_build if file_srcs != srcs else None,
   235          requires = ['go', 'cc_hdrs'],
   236      )
   237  
   238      # Compile the various bits
   239      c_rule = c_library(
   240          name = '_%s#c' % name,
   241          srcs = [cgo_rule + '|c'] + c_srcs,
   242          hdrs = [cgo_rule + '|h'] + hdrs,
   243          compiler_flags = compiler_flags + [
   244              '-Wno-error',
   245              '-Wno-unused-parameter',  # Generated code doesn't compile clean
   246          ],
   247          pkg_config_libs = pkg_config,
   248          test_only = test_only,
   249          deps = deps,
   250      )
   251      go_rule = go_library(
   252          name = '_%s#go' % name,
   253          srcs = [cgo_rule + '|go'] + go_srcs,
   254          test_only = test_only,
   255          complete = False,
   256          deps = deps,
   257      )
   258      # And finally combine the compiled C code into the Go archive object so go tool link can find it later.
   259      return _merge_cgo_obj(
   260          name = name,
   261          a_rule = ':_%s#go' % name,
   262          o_rule = c_rule,
   263          visibility = visibility,
   264          test_only = test_only,
   265          linker_flags = linker_flags,
   266          out=out,
   267          provides = {
   268              'go': ':' + name,
   269              'go_src': go_rule,
   270              'cgo': c_rule,
   271          },
   272          deps = deps,
   273      )
   274  
   275  
   276  def _merge_cgo_obj(name, a_rule, o_rule=None, visibility=None, test_only=False, tag='',
   277                     linker_flags=None, deps=None, provides=None, out=None):
   278      """Defines a rule to merge a cgo object into a Go library."""
   279      if o_rule:
   280          cmd = 'cp $SRCS_A $OUT && chmod +w $OUT && $TOOLS_AR x $SRCS_O && $TOOLS_GO tool pack r $OUT *.o'
   281      else:
   282          cmd = 'cp $SRCS_A $OUT && chmod +w $OUT && $TOOLS_AR x $PKG/*#c.a && $TOOLS_GO tool pack r $OUT *.o'
   283  
   284      return build_rule(
   285          name = name,
   286          tag = tag,
   287          srcs = {
   288              'a': [a_rule],
   289              'o': [o_rule] if o_rule else [],
   290          },
   291          outs = [out or name + '.a'],
   292          cmd = cmd,
   293          tools = {
   294              'go': [CONFIG.GO_TOOL],
   295              'ar': [CONFIG.AR_TOOL],
   296          },
   297          visibility = visibility,
   298          test_only = test_only,
   299          labels = ['cc:ld:' + flag for flag in (linker_flags or [])],
   300          requires = ['go', 'cgo'],
   301          provides = provides,
   302          deps = deps,
   303      )
   304  
   305  
   306  def go_binary(name:str, srcs:list=[], asm_srcs:list=[], out:str=None, deps:list=[],
   307                visibility:list=None, test_only:bool&testonly=False, static:bool=CONFIG.GO_DEFAULT_STATIC,
   308                filter_srcs:bool=True):
   309      """Compiles a Go binary.
   310  
   311      Args:
   312        name (str): Name of the rule.
   313        srcs (list): Go source files, one of which contains the main function.
   314        asm_srcs (list): Assembly source files.
   315        out (str): Name of the output file to create. Defaults to the same as `name`.
   316        deps (list): Dependencies
   317        visibility (list): Visibility specification
   318        test_only (bool): If True, is only visible to test rules.
   319        static (bool): If True, passes flags to the linker to try to force fully static linking.
   320                       (specifically `-linkmode external -extldflags static`).
   321                       Typically this increases size & link time a little but in return the binary
   322                       has absolutely no external dependencies.
   323                       Note that it may have negative consequences if the binary contains any cgo
   324                       (including net/http DNS lookup code potentially).
   325        filter_srcs (bool): If True, filters source files through Go's standard build constraints.
   326      """
   327      go_library(
   328          name='_%s#lib' % name,
   329          srcs=srcs or [name + '.go'],
   330          filter_srcs=filter_srcs,
   331          asm_srcs=asm_srcs,
   332          deps=deps,
   333          test_only=test_only,
   334      )
   335      cmds, tools = _go_binary_cmds(static=static)
   336      return build_rule(
   337          name=name,
   338          srcs=[':_%s#lib' % name],
   339          deps=deps,
   340          outs=[out or name],
   341          cmd=cmds,
   342          building_description="Linking...",
   343          needs_transitive_deps=True,
   344          binary=True,
   345          output_is_complete=True,
   346          test_only=test_only,
   347          tools=tools,
   348          visibility=visibility,
   349          requires=['go'],
   350          pre_build=_collect_linker_flags(static),
   351      )
   352  
   353  
   354  def go_test(name:str, srcs:list, data:list=None, deps:list=[], visibility:list=None,
   355              flags:str='', container:bool|dict=False, sandbox:bool=None, cgo:bool=False,
   356              external:bool=False, timeout:int=0, flaky:bool|int=0, test_outputs:list=None,
   357              labels:list&features&tags=None, size:str=None, static:bool=CONFIG.GO_DEFAULT_STATIC):
   358      """Defines a Go test rule.
   359  
   360      Args:
   361        name (str): Name of the rule.
   362        srcs (list): Go source files to compile.
   363        data (list): Runtime data files for the test.
   364        deps (list): Dependencies
   365        visibility (list): Visibility specification
   366        flags (str): Flags to apply to the test invocation.
   367        container (bool | dict): True to run this test in a container.
   368        sandbox (bool): Sandbox the test on Linux to restrict access to namespaces such as network.
   369        cgo (bool): True if this test depends on a cgo_library.
   370        external (bool): True if this test is external to the library it's testing, i.e. it uses the
   371                         feature of Go that allows it to be in the same directory with a _test suffix.
   372        timeout (int): Timeout in seconds to allow the test to run for.
   373        flaky (int | bool): True to mark the test as flaky, or an integer to specify how many reruns.
   374        test_outputs (list): Extra test output files to generate from this test.
   375        labels (list): Labels for this rule.
   376        size (str): Test size (enormous, large, medium or small).
   377        static (bool): If True, passes flags to the linker to try to force fully static linking.
   378                       (specifically `-linkmode external -extldflags static`).
   379                       Typically this increases size & link time a little but in return the binary
   380                       has absolutely no external dependencies.
   381                       Note that it may have negative consequences if the binary contains any cgo
   382                       (including net/http DNS lookup code potentially).
   383      """
   384      timeout, labels = _test_size_and_timeout(size, timeout, labels)
   385      # Unfortunately we have to recompile this to build the test together with its library.
   386      lib_rule = go_library(
   387          name = '_%s#lib' % name,
   388          srcs = srcs,
   389          out = name + ('_lib.a' if cgo else '.a'),
   390          deps = deps,
   391          test_only = True,
   392          _all_srcs = not external,
   393          complete = False,
   394      )
   395      if cgo:
   396          lib_rule = _merge_cgo_obj(
   397              name = name,
   398              tag = 'cgo',
   399              a_rule = lib_rule,
   400              visibility = visibility,
   401              test_only = True,
   402              deps = deps,
   403          )
   404      build_rule(
   405          name='_%s#main' % name,
   406          srcs=srcs,
   407          outs=[name + '_main.go'],
   408          deps=deps,
   409          cmd={
   410              'dbg': '$TOOLS_TEST $TOOLS_GO -o $OUT $SRCS',
   411              'opt': '$TOOLS_TEST $TOOLS_GO -o $OUT $SRCS',
   412              'cover': '$TOOLS_TEST $TOOLS_GO -d . -o $OUT $SRCS ',
   413          },
   414          needs_transitive_deps=True,  # Need all .a files to template coverage variables
   415          requires=['go'],
   416          test_only=True,
   417          tools={
   418              'go': [CONFIG.GO_TOOL],
   419              'test': [CONFIG.GO_TEST_TOOL],
   420          },
   421          post_build=lambda name, output: _replace_test_package(name, output, static),
   422      )
   423      deps.append(lib_rule)
   424      go_library(
   425          name='_%s#main_lib' % name,
   426          srcs=[':_%s#main' % name],
   427          deps=deps,
   428          _needs_transitive_deps=True,  # Rather annoyingly this is only needed for coverage
   429          test_only=True,
   430      )
   431      cmds, tools = _go_binary_cmds(static=static)
   432      build_rule(
   433          name=name,
   434          srcs=[':_%s#main_lib' % name],
   435          data=data,
   436          deps=deps,
   437          outs=[name],
   438          tools=tools,
   439          cmd=cmds,
   440          test_cmd='$TEST %s | tee test.results' % flags,
   441          visibility=visibility,
   442          container=container,
   443          test_sandbox=sandbox,
   444          test_timeout=timeout,
   445          flaky=flaky,
   446          test_outputs=test_outputs,
   447          requires=['go'],
   448          labels=labels,
   449          binary=True,
   450          test=True,
   451          building_description="Compiling...",
   452          needs_transitive_deps=True,
   453          output_is_complete=True,
   454      )
   455  
   456  
   457  def cgo_test(name:str, srcs:list, data:list=None, deps:list=None, visibility:list=None,
   458               flags:str='', container:bool|dict=False, sandbox:bool=None, timeout:int=0, flaky:bool|int=0,
   459               test_outputs:list=None, labels:list&features&tags=None, size:str=None, static:bool=False):
   460      """Defines a Go test rule over a cgo_library.
   461  
   462      If the library you are testing is a cgo_library, you must use this instead of go_test.
   463      It's ok to depend on a cgo_library though as long as it's not the same package
   464      as your test; in that (any any other case of testing a go_library) you must use go_test.
   465  
   466      Args:
   467        name (str): Name of the rule.
   468        srcs (list): Go source files to compile.
   469        data (list): Runtime data files for the test.
   470        deps (list): Dependencies
   471        visibility (list): Visibility specification
   472        flags (str): Flags to apply to the test invocation.
   473        container (bool | dict): True to run this test in a container.
   474        sandbox (bool): Sandbox the test on Linux to restrict access to namespaces such as network.
   475        timeout (int): Timeout in seconds to allow the test to run for.
   476        flaky (int | bool): True to mark the test as flaky, or an integer to specify how many reruns.
   477        test_outputs (list): Extra test output files to generate from this test.
   478        labels (list): Labels for this rule.
   479        size (str): Test size (enormous, large, medium or small).
   480        static (bool): If True, passes flags to the linker to try to force fully static linking.
   481                       (specifically `-linkmode external -extldflags static`).
   482                       Typically this increases size & link time a little but in return the binary
   483                       has absolutely no external dependencies.
   484                       It may not be easy to make cgo tests work when linked statically; depending
   485                       on your toolchain it may not be possible or may fail.
   486      """
   487      go_test(
   488          name = name,
   489          srcs = srcs,
   490          data = data,
   491          deps = deps,
   492          cgo = True,
   493          static = static,
   494          visibility = visibility,
   495          flags = flags,
   496          container = container,
   497          sandbox = sandbox,
   498          timeout = timeout,
   499          flaky = flaky,
   500          test_outputs = test_outputs,
   501          labels = labels,
   502          size = size,
   503      )
   504  
   505  
   506  def go_get(name:str, get:str|list, repo:str='', deps:list=[], exported_deps:list=None,
   507             visibility:list=None, patch:str=None, binary:bool=False, test_only:bool&testonly=False,
   508             install:list=None, revision:str|list=None, strip:list=None, hashes:list=None,
   509             extra_outs:list=[]):
   510      """Defines a dependency on a third-party Go library.
   511  
   512      Note that unlike a normal `go get` call, this does *not* install transitive dependencies.
   513      You will need to add those as separate rules; `go list -f '{{.Deps}}' <package>` can be
   514      useful to discover what they should be.
   515  
   516      Note also that while a single go_get is sufficient to compile all parts of a library,
   517      one may also want separate ones for a binary. Since two rules can't both output the same
   518      source files (and you only want to download them once anyway) you should handle that by
   519      marking the non-binary rule as a dependency of the binary one - if you don't there may
   520      be warnings issued about conflicting output files.
   521  
   522      Args:
   523        name (str): Name of the rule
   524        get (str): Target to get (eg. "github.com/gorilla/mux"). Can also be a list of multiple in
   525                   which case they are fetched separately and compiled together, which can be useful
   526                   for avoiding issues with circular dependencies.
   527        repo (str): Location of a Git repository to fetch from.
   528        deps (list): Dependencies
   529        exported_deps (list): Dependencies to make available to anything using this rule.
   530        visibility (list): Visibility specification
   531        patch (str): Patch file to apply
   532        binary (bool): True if the output of the rule is a binary.
   533        test_only (bool): If true this rule will only be visible to tests.
   534        install (list): Allows specifying the exact list of packages to install. If this is not passed,
   535                        the package passed to 'get' is installed. If you pass this for subpackages, you
   536                        will need to explicitly add an empty string to the list if you want to install
   537                        the root package from this repo.
   538        revision (str): Git hash to check out before building. Only works for git at present,
   539                        not for other version control systems.
   540        strip (list): List of paths to strip from the installed target.
   541        hashes (list): List of hashes to verify the downloaded sources against.
   542        extra_outs (list): List of additional output files after the compile.
   543      """
   544      if isinstance(get, str):
   545          get = [get]
   546          revision = [revision]
   547          tags = ['get']
   548      else:
   549          tags = [basename(dirname(g)) for g in get]
   550          revision = revision or [None for g in get]
   551      all_installs = []
   552      outs = extra_outs
   553      provides = None
   554      for getone, revision, tag in zip(get, revision, tags):
   555          get_rule, getroot = _go_get_download(
   556              name = name,
   557              tag = tag,
   558              get = getone,
   559              repo = repo,
   560              patch = patch,
   561              hashes = hashes,
   562              test_only = test_only,
   563              revision = revision,
   564              strip = strip,
   565          )
   566          outs += [getroot]
   567          deps += [get_rule]
   568          provides = {'go': ':' + name, 'go_src': get_rule}
   569          if install:
   570              all_installs += [i if i.startswith(getroot) else (getroot + '/' + i) for i in install]
   571          else:
   572              all_installs += [getone]
   573  
   574      # Now compile it in a separate step.
   575      cmd = [
   576          'export GOPATH="%s"' % CONFIG.GOPATH,
   577          '$TOOL install -gcflags "-trimpath $TMP_DIR" ' + ' '.join(all_installs or install),
   578      ]
   579      if package_name():
   580          cmd += [
   581              # The outputs tend to end up in subdirectories (Go seems to match them to the location the source came from)
   582              'rm -rf bin' if binary else 'rm -rf pkg',
   583              'mv $PKG_DIR/bin .' if binary else 'mv $PKG_DIR/pkg .',
   584          ]
   585      if binary:
   586          outs = ['bin/' + name]
   587      else:
   588          # Outputs are created one directory down from where we want them.
   589          # For most it doesn't matter but the top-level one will get lost.
   590          cmd += [' if [ -f ${OUTS}.a ]; then mkdir -p $OUTS && mv ${OUTS}.a $OUTS; fi']
   591          outs = ['pkg/%s_%s/%s' % (CONFIG.OS, CONFIG.ARCH, out) for out in outs]
   592      return build_rule(
   593          name = name,
   594          outs = outs,
   595          deps = deps,
   596          exported_deps = exported_deps,
   597          tools = [CONFIG.GO_TOOL],
   598          visibility = visibility,
   599          building_description = 'Compiling...',
   600          cmd = ' && '.join(cmd),
   601          binary = binary,
   602          requires = ['go'],
   603          test_only = test_only,
   604          sandbox = False,
   605          needs_transitive_deps = True,
   606          provides = provides,
   607      )
   608  
   609  
   610  def _go_get_download(name:str, tag:str, get:str, repo:str='', patch:str=None, hashes:list=None,
   611                       test_only:bool&testonly=False, revision:str=None, strip:list=None,
   612                       labels:list=[]):
   613      if hashes and not revision:
   614          log.warning("You shouldn't specify hashes on go_get without specifying revision as well")
   615      labels = ['go_get:%s@%s' % (get, revision) if revision else 'go_get:%s' % get]
   616      getroot = get[:-4] if get.endswith('/...') else get
   617      subdir = 'src/' + getroot
   618  
   619      # Some special optimisation for github, which lets us download zipfiles at a particular sha instead of
   620      # cloning the whole repo. Obviously that is a lot faster than cloning entire repos.
   621      if get.startswith('github.com') and revision and get.count('/') >= 2:
   622          cmd, get_deps, tools = _go_github_repo_cmd(name, getroot, getroot, revision)
   623      elif get.startswith('golang.org/x/') and revision and not repo:
   624          # We know about these guys...
   625          cmd, get_deps, tools = _go_github_repo_cmd(name, getroot, 'github.com/golang/' + getroot[len('golang.org/x/'):], revision)
   626      else:
   627          get_deps = []
   628          if repo:
   629              # we've been told exactly where to get the source from.
   630              cmd = ['git clone %s src/%s' % (repo, getroot)]
   631          else:
   632              # Ultimate fallback to go get.
   633              # This has some more restrictions than the above (e.g. go get won't fetch a directory
   634              # with no Go files in it, even if passed -d).
   635              cmd = [
   636                  'rm -rf src pkg',
   637                  'export GOPATH="$TMP_DIR"',
   638                  '$TOOL get -d ' + get,
   639              ]
   640          if revision:
   641              # Annoyingly -C does not work on git checkout :(
   642              cmd.append('(cd %s && git checkout -q %s)' % (subdir, revision))
   643          cmd.append('find . -name .git | xargs rm -rf')
   644          tools = [CONFIG.GO_TOOL]
   645      if patch:
   646          cmd.append('patch -s -d %s -p1 < ${TMP_DIR}/$SRCS_PATCH' % subdir)
   647      if strip:
   648          cmd += ['rm -rf %s/%s' % (subdir, s) for s in strip]
   649      return build_rule(
   650          name = name,
   651          tag = tag,
   652          srcs = {
   653              'patch': [patch],
   654              'get': get_deps,
   655          },
   656          outs = [subdir],
   657          tools = tools,
   658          building_description = 'Fetching...',
   659          cmd = ' && '.join(cmd),
   660          requires = ['go'],
   661          test_only = test_only,
   662          labels = labels,
   663          hashes = hashes,
   664          sandbox = False,
   665      ), getroot
   666  
   667  
   668  def _go_github_repo_cmd(name, get, repo, revision):
   669      """Returns a partial command to fetch a Go repo from Github."""
   670      parts = get.split('/')
   671      out = '/'.join(parts[:3])
   672      if repo.count('/') > 2:
   673          parts = repo.split('/')
   674          repo = '/'.join(parts[:3])
   675      remote_rule = remote_file(
   676          name = name,
   677          _tag = 'download',
   678          url = 'https://%s/archive/%s.zip' % (repo, revision),
   679          out = name + '.zip',
   680      )
   681      return [
   682          'rm -rf src/' + out,
   683          '$TOOL x $SRCS_GET',
   684          'mv %s* src/%s' % (parts[2], out),
   685      ], [remote_rule], [CONFIG.JARCAT_TOOL]
   686  
   687  
   688  def _replace_test_package(name, output, static):
   689      """Post-build function, called after we template the main function.
   690  
   691      The purpose is to replace the real library with the specific one we've
   692      built for this test which has the actual test functions in it.
   693      """
   694      if not name.endswith('#main') or not name.startswith('_'):
   695          raise ValueError('unexpected rule name: ' + name)
   696      lib = name[:-5] + '#main_lib'
   697      new_name = name[1:-5]
   698      for line in output:
   699          if line.startswith('Package: '):
   700              ldflags, pkg_config = _get_ldflags_and_pkgconfig(name)
   701              pkg_name = line[9:]
   702              name_changed = pkg_name != new_name
   703              if name_changed or ldflags or pkg_config:  # Might not be necessary if names match already.
   704                  binary_cmds, _ = _go_binary_cmds(static=static, ldflags=ldflags, pkg_config=pkg_config)
   705                  if name_changed:
   706                      for k, v in binary_cmds.items():
   707                          set_command(new_name, k, 'mv -f ${PKG_DIR}/%s.a ${PKG_DIR}/%s.a && %s' % (new_name, pkg_name, v))
   708                  else:
   709                      for k, v in binary_cmds.items():
   710                          set_command(new_name, k, v)
   711              if name_changed:
   712                  for k, v in _go_library_cmds().items():
   713                      set_command(lib, k, 'mv -f ${PKG_DIR}/%s.a ${PKG_DIR}/%s.a && %s' % (new_name, pkg_name, v))
   714  
   715  
   716  def _go_library_cmds(complete=True, all_srcs=False, cover=True, filter_srcs=True):
   717      """Returns the commands to run for building a Go library."""
   718      filter_cmd = 'export SRCS="$(${TOOLS_FILTER} ${SRCS})"; ' if filter_srcs else ''
   719      # Invokes the Go compiler.
   720      complete_flag = '-complete ' if complete else ''
   721      compile_cmd = '$TOOLS_GO tool compile -trimpath $TMP_DIR %s%s -pack -o $OUT ' % (complete_flag, _GOPATH)
   722      # Annotates files for coverage.
   723      cover_cmd = 'for SRC in $SRCS; do BN=$(basename $SRC); go tool cover -mode=set -var=GoCover_${BN//[.-]/_} $SRC > _tmp.go && mv -f _tmp.go $SRC; done'
   724      prefix = ('export SRCS="$PKG_DIR/*.go"; ' + _LINK_PKGS_CMD) if all_srcs else _LINK_PKGS_CMD
   725      prefix += _go_import_path_cmd(CONFIG.GO_IMPORT_PATH)
   726      cmds = {
   727          'dbg': '%s; %s%s -N -l $SRCS' % (prefix, filter_cmd, compile_cmd),
   728          'opt': '%s; %s%s $SRCS' % (prefix, filter_cmd, compile_cmd),
   729      }
   730      if cover:
   731          cmds['cover'] = '%s; %s%s && %s $SRCS' % (prefix, filter_cmd, cover_cmd, compile_cmd)
   732      return cmds
   733  
   734  
   735  def _go_binary_cmds(static=False, ldflags='', pkg_config=''):
   736      """Returns the commands to run for linking a Go binary."""
   737      _link_cmd = '$TOOLS_GO tool link -tmpdir $TMP_DIR -extld $TOOLS_LD %s -L . -o ${OUT} ' % _GOPATH.replace('-I ', '-L ')
   738      prefix = _LINK_PKGS_CMD + _go_import_path_cmd(CONFIG.GO_IMPORT_PATH)
   739  
   740      if static:
   741          flags = '-linkmode external -extldflags "-static %s %s"' % (ldflags, pkg_config)
   742      elif ldflags or pkg_config:
   743          flags = '-extldflags "%s %s"' % (ldflags, pkg_config)
   744      else:
   745          flags = ''
   746  
   747      return {
   748          'dbg': '%s && %s %s $SRCS' % (prefix, _link_cmd, flags),
   749          'opt': '%s && %s %s -s -w $SRCS' % (prefix, _link_cmd, flags),
   750      }, {
   751          'go': [CONFIG.GO_TOOL],
   752          'ld': [CONFIG.LD_TOOL if CONFIG.LINK_WITH_LD_TOOL else CONFIG.CC_TOOL],
   753      }
   754  
   755  
   756  def _go_import_path_cmd(import_path):
   757      """Returns a partial command which is used for setting up the Go import path."""
   758      if not import_path:
   759          return ''
   760      elif import_path.startswith('/'):
   761          raise ConfigError('GO_IMPORT_PATH cannot start with a /')
   762      elif '/' in import_path:
   763          return ' && mkdir -p %s && ln -s $TMP_DIR %s' % (dirname(import_path), import_path)
   764      else:
   765          return ' && ln -s $TMP_DIR ' + import_path
   766  
   767  
   768  def _collect_linker_flags(static):
   769      """Returns a pre-build function to apply transitive linker flags to a go_binary rule."""
   770      def collect_linker_flags(name):
   771          ldflags, pkg_config = _get_ldflags_and_pkgconfig(name)
   772          if ldflags or pkg_config:
   773              cmds, _ =  _go_binary_cmds(static=static, ldflags=ldflags, pkg_config=pkg_config)
   774              for k, v in cmds.items():
   775                  set_command(name, k, v)
   776      return collect_linker_flags
   777  
   778  
   779  def _get_ldflags_and_pkgconfig(name):
   780      """Returns the ldflags and pkg-config invocations for a target."""
   781      labels = get_labels(name, 'cc:')
   782      ldflags = ' '.join([l[3:] for l in labels if l.startswith('ld:')])
   783      pkg_config = ' '.join([l[3:] for l in labels if l.startswith('pc:')])
   784      return (ldflags, '`pkg-config --libs %s`' % pkg_config) if pkg_config else (ldflags, '')