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