From 586e09d9f03c1faa1f3f783b17eab8916ef8d633 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Thu, 29 Sep 2022 13:58:08 -0700 Subject: [PATCH 1/2] Migrate go subsystem to use environmentaware where appropriate # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- .../pants/backend/go/subsystems/golang.py | 259 +++++++++--------- src/python/pants/backend/go/util_rules/cgo.py | 47 ++-- .../backend/go/util_rules/cgo_binaries.py | 6 +- .../backend/go/util_rules/go_bootstrap.py | 29 +- src/python/pants/backend/go/util_rules/sdk.py | 4 +- 5 files changed, 172 insertions(+), 173 deletions(-) diff --git a/src/python/pants/backend/go/subsystems/golang.py b/src/python/pants/backend/go/subsystems/golang.py index daac1a3ef92..8fa1b9873a5 100644 --- a/src/python/pants/backend/go/subsystems/golang.py +++ b/src/python/pants/backend/go/subsystems/golang.py @@ -6,10 +6,9 @@ import logging import os -from pants.engine.env_vars import EnvironmentVars from pants.option.option_types import BoolOption, StrListOption, StrOption from pants.option.subsystem import Subsystem -from pants.util.memo import memoized_method +from pants.util.memo import memoized_property from pants.util.ordered_set import OrderedSet from pants.util.strutil import softwrap @@ -23,24 +22,139 @@ class GolangSubsystem(Subsystem): options_scope = "golang" help = "Options for Golang support." - _go_search_paths = StrListOption( - default=[""], - help=softwrap( - """ - A list of paths to search for Go. + class EnvironmentAware(Subsystem.EnvironmentAware): + + depends_on_env_vars = ("PATH",) - Specify absolute paths to directories with the `go` binary, e.g. `/usr/bin`. - Earlier entries will be searched first. + _go_search_paths = StrListOption( + default=[""], + help=softwrap( + """ + A list of paths to search for Go. - The following special strings are supported: + Specify absolute paths to directories with the `go` binary, e.g. `/usr/bin`. + Earlier entries will be searched first. - * ``, the contents of the PATH environment variable - * ``, all Go versions currently configured by ASDF \ - `(asdf shell, ${HOME}/.tool-versions)`, with a fallback to all installed versions - * ``, the ASDF interpreter with the version in BUILD_ROOT/.tool-versions + The following special strings are supported: + + * ``, the contents of the PATH environment variable + * ``, all Go versions currently configured by ASDF \ + `(asdf shell, ${HOME}/.tool-versions)`, with a fallback to all installed versions + * ``, the ASDF interpreter with the version in BUILD_ROOT/.tool-versions + """ + ), + ) + _subprocess_env_vars = StrListOption( + default=["LANG", "LC_CTYPE", "LC_ALL", "PATH"], + help=softwrap( + """ + Environment variables to set when invoking the `go` tool. + Entries are either strings in the form `ENV_VAR=value` to set an explicit value; + or just `ENV_VAR` to copy the value from Pants's own environment. """ - ), - ) + ), + advanced=True, + ) + + _cgo_tool_search_paths = StrListOption( + default=[""], + help=softwrap( + """ + A list of paths to search for tools needed by CGo (e.g., gcc, g++). + + Specify absolute paths to directories with tools needed by CGo , e.g. `/usr/bin`. + Earlier entries will be searched first. + + The following special strings are supported: + + * ``, the contents of the PATH environment variable + """ + ), + ) + + cgo_gcc_binary_name = StrOption( + default="gcc", + advanced=True, + help="Name of the tool to use to compile C code included via CGo in a Go package.", + ) + + cgo_gxx_binary_name = StrOption( + default="g++", + advanced=True, + help="Name of the tool to use to compile C++ code included via CGo in a Go package.", + ) + + cgo_fortran_binary_name = StrOption( + default="gfortran", + advanced=True, + help="Name of the tool to use to compile fortran code included via CGo in a Go package.", + ) + + cgo_c_flags = StrListOption( + default=lambda _: list(_DEFAULT_COMPILER_FLAGS), + advanced=True, + help=softwrap( + """ + Compiler options used when compiling C code when Cgo is enabled. Equivalent to setting the + CGO_CFLAGS environment variable when invoking `go`. + """ + ), + ) + + cgo_cxx_flags = StrListOption( + default=lambda _: list(_DEFAULT_COMPILER_FLAGS), + advanced=True, + help=softwrap( + """ + Compiler options used when compiling C++ code when Cgo is enabled. Equivalent to setting the + CGO_CXXFLAGS environment variable when invoking `go`. + """ + ), + ) + + cgo_fortran_flags = StrListOption( + default=lambda _: list(_DEFAULT_COMPILER_FLAGS), + advanced=True, + help=softwrap( + """ + Compiler options used when compiling Fortran code when Cgo is enabled. Equivalent to setting the + CGO_FFLAGS environment variable when invoking `go`. + """ + ), + ) + + cgo_linker_flags = StrListOption( + default=lambda _: list(_DEFAULT_COMPILER_FLAGS), + advanced=True, + help=softwrap( + """ + Compiler options used when linking native code when Cgo is enabled. Equivalent to setting the + CGO_LDFLAGS environment variable when invoking `go`. + """ + ), + ) + + @property + def raw_go_search_paths(self) -> tuple[str, ...]: + return tuple(self._go_search_paths) + + @property + def env_vars_to_pass_to_subprocesses(self) -> tuple[str, ...]: + return tuple(sorted(set(self._subprocess_env_vars))) + + @memoized_property + def cgo_tool_search_paths(self) -> tuple[str, ...]: + def iter_path_entries(): + for entry in self._cgo_tool_search_paths: + if entry == "": + path = self.env_vars.get("PATH") + if path: + yield from path.split(os.pathsep) + else: + yield entry + + return tuple(OrderedSet(iter_path_entries())) + minimum_expected_version = StrOption( default="1.17", help=softwrap( @@ -57,18 +171,6 @@ class GolangSubsystem(Subsystem): """ ), ) - _subprocess_env_vars = StrListOption( - default=["LANG", "LC_CTYPE", "LC_ALL", "PATH"], - help=softwrap( - """ - Environment variables to set when invoking the `go` tool. - Entries are either strings in the form `ENV_VAR=value` to set an explicit value; - or just `ENV_VAR` to copy the value from Pants's own environment. - """ - ), - advanced=True, - ) - tailor_go_mod_targets = BoolOption( default=True, help=softwrap( @@ -105,7 +207,7 @@ class GolangSubsystem(Subsystem): default=False, help=softwrap( """\ - Enable Cgos support, which allows Go and C code to interact. This option must be enabled for any + Enable Cgo support, which allows Go and C code to interact. This option must be enabled for any packages making use of Cgo to actually be compiled with Cgo support. TODO: Future Pants changes may also require enabling Cgo via fields on relevant Go targets. @@ -116,84 +218,6 @@ class GolangSubsystem(Subsystem): ), ) - _cgo_tool_search_paths = StrListOption( - default=[""], - help=softwrap( - """ - A list of paths to search for tools needed by CGo (e.g., gcc, g++). - - Specify absolute paths to directories with tools needed by CGo , e.g. `/usr/bin`. - Earlier entries will be searched first. - - The following special strings are supported: - - * ``, the contents of the PATH environment variable - """ - ), - ) - - cgo_gcc_binary_name = StrOption( - default="gcc", - advanced=True, - help="Name of the tool to use to compile C code included via CGo in a Go package.", - ) - - cgo_gxx_binary_name = StrOption( - default="g++", - advanced=True, - help="Name of the tool to use to compile C++ code included via CGo in a Go package.", - ) - - cgo_fortran_binary_name = StrOption( - default="gfortran", - advanced=True, - help="Name of the tool to use to compile fortran code included via CGo in a Go package.", - ) - - cgo_c_flags = StrListOption( - default=lambda _: list(_DEFAULT_COMPILER_FLAGS), - advanced=True, - help=softwrap( - """ - Compiler options used when compiling C code when Cgo is enabled. Equivalent to setting the - CGO_CFLAGS environment variable when invoking `go`. - """ - ), - ) - - cgo_cxx_flags = StrListOption( - default=lambda _: list(_DEFAULT_COMPILER_FLAGS), - advanced=True, - help=softwrap( - """ - Compiler options used when compiling C++ code when Cgo is enabled. Equivalent to setting the - CGO_CXXFLAGS environment variable when invoking `go`. - """ - ), - ) - - cgo_fortran_flags = StrListOption( - default=lambda _: list(_DEFAULT_COMPILER_FLAGS), - advanced=True, - help=softwrap( - """ - Compiler options used when compiling Fortran code when Cgo is enabled. Equivalent to setting the - CGO_FFLAGS environment variable when invoking `go`. - """ - ), - ) - - cgo_linker_flags = StrListOption( - default=lambda _: list(_DEFAULT_COMPILER_FLAGS), - advanced=True, - help=softwrap( - """ - Compiler options used when linking native code when Cgo is enabled. Equivalent to setting the - CGO_LDFLAGS environment variable when invoking `go`. - """ - ), - ) - asdf_tool_name = StrOption( default="go-sdk", help=softwrap( @@ -218,24 +242,3 @@ class GolangSubsystem(Subsystem): ), advanced=True, ) - - @property - def raw_go_search_paths(self) -> tuple[str, ...]: - return tuple(self._go_search_paths) - - @property - def env_vars_to_pass_to_subprocesses(self) -> tuple[str, ...]: - return tuple(sorted(set(self._subprocess_env_vars))) - - @memoized_method - def cgo_tool_search_paths(self, env: EnvironmentVars) -> tuple[str, ...]: - def iter_path_entries(): - for entry in self._cgo_tool_search_paths: - if entry == "": - path = env.get("PATH") - if path: - yield from path.split(os.pathsep) - else: - yield entry - - return tuple(OrderedSet(iter_path_entries())) diff --git a/src/python/pants/backend/go/util_rules/cgo.py b/src/python/pants/backend/go/util_rules/cgo.py index a529de75134..7e18c1d9d96 100644 --- a/src/python/pants/backend/go/util_rules/cgo.py +++ b/src/python/pants/backend/go/util_rules/cgo.py @@ -313,7 +313,7 @@ async def _cc( flags: Iterable[str], obj_file: str, description: str, - golang_subsystem: GolangSubsystem, + golang_env_aware: GolangSubsystem.EnvironmentAware, ) -> Process: compiler_path = await Get( BinaryPath, @@ -328,7 +328,7 @@ async def _cc( Get(SetupCompilerCmdResult, SetupCompilerCmdRequest((compiler_path.path,), work_dir)), Get( EnvironmentVars, - EnvironmentVarsRequest(golang_subsystem.env_vars_to_pass_to_subprocesses), + EnvironmentVarsRequest(golang_env_aware.env_vars_to_pass_to_subprocesses), ), Get(Digest, MergeDigests([input_digest, wrapper_script.digest])), ) @@ -361,7 +361,6 @@ async def _gccld( flags: Iterable[str], objs: Iterable[str], description: str, - golang_subsystem: GolangSubsystem, ) -> FallibleProcessResult: compiler_path = await Get( BinaryPath, @@ -420,18 +419,18 @@ async def _dynimport( pkg_name: str, goroot: GoRoot, import_go_path: str, - golang_subsystem: GolangSubsystem, + golang_env_aware: GolangSubsystem.EnvironmentAware, use_cxx_linker: bool, ) -> _DynImportResult: cgo_main_compile_process = await _cc( - binary_name=golang_subsystem.cgo_gcc_binary_name, + binary_name=golang_env_aware.cgo_gcc_binary_name, input_digest=input_digest, work_dir=obj_dir_path, src_file=os.path.join(obj_dir_path, "_cgo_main.c"), flags=cflags, obj_file=os.path.join(obj_dir_path, "_cgo_main.o"), description="Compile _cgo_main.c", - golang_subsystem=golang_subsystem, + golang_env_aware=golang_env_aware, ) cgo_main_compile_result = await Get(ProcessResult, Process, cgo_main_compile_process) obj_digest = await Get( @@ -454,9 +453,9 @@ async def _dynimport( ldflags = [arg for arg in ldflags if arg != "-static"] linker_binary_name = ( - golang_subsystem.cgo_gxx_binary_name + golang_env_aware.cgo_gxx_binary_name if use_cxx_linker - else golang_subsystem.cgo_gcc_binary_name + else golang_env_aware.cgo_gcc_binary_name ) cgo_binary_link_result = await _gccld( @@ -467,7 +466,6 @@ async def _dynimport( flags=ldflags, objs=[*obj_files, os.path.join(obj_dir_path, "_cgo_main.o")], description="Link _cgo_.o", - golang_subsystem=golang_subsystem, ) if cgo_binary_link_result.exit_code != 0: # From `go` source: @@ -509,7 +507,6 @@ async def _dynimport( flags=[*ldflags, allow_unresolved_symbols_ldflag], objs=obj_files, description="Link _cgo_.o", - golang_subsystem=golang_subsystem, ) if cgo_binary_link_result.exit_code != 0: raise ValueError( @@ -623,7 +620,7 @@ def _replace_srcdir_in_flags(flags: Iterable[str], dir_path: str) -> tuple[str, @rule async def cgo_compile_request( - request: CGoCompileRequest, goroot: GoRoot, golang_subsystem: GolangSubsystem + request: CGoCompileRequest, goroot: GoRoot, golang_env_aware: GolangSubsystem.EnvironmentAware ) -> CGoCompileResult: dir_path = request.dir_path if request.dir_path else "." obj_dir_path = f"{dir_path}/__obj__" if dir_path else "__obj__" @@ -636,10 +633,10 @@ async def cgo_compile_request( # directives. flags = dataclasses.replace( flags, - cflags=golang_subsystem.cgo_c_flags + flags.cflags, - cxxflags=golang_subsystem.cgo_cxx_flags + flags.cxxflags, - fflags=golang_subsystem.cgo_fortran_flags + flags.fflags, - ldflags=golang_subsystem.cgo_linker_flags + flags.ldflags, + cflags=golang_env_aware.cgo_c_flags + flags.cflags, + cxxflags=golang_env_aware.cgo_cxx_flags + flags.cxxflags, + fflags=golang_env_aware.cgo_fortran_flags + flags.fflags, + ldflags=golang_env_aware.cgo_linker_flags + flags.ldflags, ) # Resolve pkg-config flags into compiler and linker flags. @@ -667,7 +664,7 @@ async def cgo_compile_request( # Likewise for Fortran, except there are many Fortran compilers. # Support gfortran out of the box and let others pass the correct link options # via CGO_LDFLAGS - if request.fortran_files and "gfortran" in golang_subsystem.cgo_fortran_binary_name: + if request.fortran_files and "gfortran" in golang_env_aware.cgo_fortran_binary_name: flags = dataclasses.replace(flags, ldflags=flags.ldflags + ("-lgfortran",)) # TODO(#16838): Add MSan (memory sanitizer) option. @@ -761,14 +758,14 @@ async def cgo_compile_request( out_obj_files.append(ofile) compile_process = await _cc( - binary_name=golang_subsystem.cgo_gcc_binary_name, + binary_name=golang_env_aware.cgo_gcc_binary_name, input_digest=cgo_result.output_digest, work_dir=obj_dir_path, src_file=gcc_file, flags=cflags, obj_file=ofile, description=f"Compile cgo source: {gcc_file}", - golang_subsystem=golang_subsystem, + golang_env_aware=golang_env_aware, ) compile_process_gets.append(Get(ProcessResult, Process, compile_process)) @@ -780,14 +777,14 @@ async def cgo_compile_request( out_obj_files.append(ofile) compile_process = await _cc( - binary_name=golang_subsystem.cgo_gxx_binary_name, + binary_name=golang_env_aware.cgo_gxx_binary_name, input_digest=cgo_result.output_digest, work_dir=obj_dir_path, src_file=cxx_file, flags=cxxflags, obj_file=ofile, description=f"Compile cgo C++ source: {cxx_file}", - golang_subsystem=golang_subsystem, + golang_env_aware=golang_env_aware, ) compile_process_gets.append(Get(ProcessResult, Process, compile_process)) @@ -798,14 +795,14 @@ async def cgo_compile_request( out_obj_files.append(ofile) compile_process = await _cc( - binary_name=golang_subsystem.cgo_gcc_binary_name, + binary_name=golang_env_aware.cgo_gcc_binary_name, input_digest=cgo_result.output_digest, work_dir=obj_dir_path, src_file=objc_file, flags=cflags, obj_file=ofile, description=f"Compile cgo Objective-C source: {objc_file}", - golang_subsystem=golang_subsystem, + golang_env_aware=golang_env_aware, ) compile_process_gets.append(Get(ProcessResult, Process, compile_process)) @@ -818,14 +815,14 @@ async def cgo_compile_request( out_obj_files.append(ofile) compile_process = await _cc( - binary_name=golang_subsystem.cgo_fortran_binary_name, + binary_name=golang_env_aware.cgo_fortran_binary_name, input_digest=cgo_result.output_digest, work_dir=obj_dir_path, src_file=fortran_file, flags=fflags, obj_file=ofile, description=f"Compile cgo Fortran source: {fortran_file}", - golang_subsystem=golang_subsystem, + golang_env_aware=golang_env_aware, ) compile_process_gets.append(Get(ProcessResult, Process, compile_process)) @@ -856,7 +853,7 @@ async def cgo_compile_request( pkg_name=request.pkg_name, goroot=goroot, import_go_path=os.path.join(obj_dir_path, "_cgo_import.go"), - golang_subsystem=golang_subsystem, + golang_env_aware=golang_env_aware, use_cxx_linker=bool(request.cxx_files), ) if dynimport_result.dyn_out_go: diff --git a/src/python/pants/backend/go/util_rules/cgo_binaries.py b/src/python/pants/backend/go/util_rules/cgo_binaries.py index 81ed324e1a0..074b98fca3f 100644 --- a/src/python/pants/backend/go/util_rules/cgo_binaries.py +++ b/src/python/pants/backend/go/util_rules/cgo_binaries.py @@ -12,7 +12,6 @@ BinaryPathTest, ) from pants.engine.engine_aware import EngineAwareParameter -from pants.engine.env_vars import EnvironmentVars, EnvironmentVarsRequest from pants.engine.internals.selectors import Get from pants.engine.rules import collect_rules, rule @@ -28,12 +27,11 @@ def debug_hint(self) -> str | None: @rule async def find_cgo_binary_path( - request: CGoBinaryPathRequest, golang_subsystem: GolangSubsystem + request: CGoBinaryPathRequest, golang_env_aware: GolangSubsystem.EnvironmentAware ) -> BinaryPath: - env = await Get(EnvironmentVars, EnvironmentVarsRequest(["PATH"])) path_request = BinaryPathRequest( binary_name=request.binary_name, - search_path=golang_subsystem.cgo_tool_search_paths(env), + search_path=golang_env_aware.cgo_tool_search_paths, test=request.binary_path_test, ) paths = await Get(BinaryPaths, BinaryPathRequest, path_request) diff --git a/src/python/pants/backend/go/util_rules/go_bootstrap.py b/src/python/pants/backend/go/util_rules/go_bootstrap.py index ec692e8ab82..632d0d70b69 100644 --- a/src/python/pants/backend/go/util_rules/go_bootstrap.py +++ b/src/python/pants/backend/go/util_rules/go_bootstrap.py @@ -9,7 +9,7 @@ from pants.backend.go.subsystems.golang import GolangSubsystem from pants.core.util_rules import asdf from pants.core.util_rules.asdf import AsdfToolPathsRequest, AsdfToolPathsResult -from pants.engine.env_vars import EnvironmentVars +from pants.engine.env_vars import EnvironmentVars, EnvironmentVarsRequest from pants.engine.internals.selectors import Get from pants.engine.rules import collect_rules, rule @@ -18,17 +18,15 @@ @dataclass(frozen=True) class GoBootstrap: - EXTRA_ENV_VAR_NAMES = ("PATH",) - raw_go_search_paths: tuple[str, ...] - environment: EnvironmentVars asdf_standard_tool_paths: tuple[str, ...] asdf_local_tool_paths: tuple[str, ...] + _path: str | None @property def go_search_paths(self) -> tuple[str, ...]: special_strings = { - "": lambda: GoBootstrap.get_environment_paths(self.environment), + "": lambda: self.environment_paths, "": lambda: self.asdf_standard_tool_paths, "": lambda: self.asdf_local_tool_paths, } @@ -41,18 +39,19 @@ def go_search_paths(self) -> tuple[str, ...]: expanded.append(s) return tuple(expanded) - @staticmethod - def get_environment_paths(env: EnvironmentVars) -> list[str]: + @property + def environment_paths(self) -> list[str]: """Returns a list of paths specified by the PATH env var.""" - pathstr = env.get("PATH") - if pathstr: - return pathstr.split(os.pathsep) + if self._path: + return self._path.split(os.pathsep) return [] @rule -async def resolve_go_bootstrap(golang_subsystem: GolangSubsystem) -> GoBootstrap: - raw_go_search_paths = golang_subsystem.raw_go_search_paths +async def resolve_go_bootstrap( + golang_subsystem: GolangSubsystem, golang_env_aware: GolangSubsystem.EnvironmentAware +) -> GoBootstrap: + raw_go_search_paths = golang_env_aware.raw_go_search_paths result = await Get( AsdfToolPathsResult, AsdfToolPathsRequest( @@ -60,17 +59,19 @@ async def resolve_go_bootstrap(golang_subsystem: GolangSubsystem) -> GoBootstrap tool_description="Go distribution", resolve_standard="" in raw_go_search_paths, resolve_local="" in raw_go_search_paths, - extra_env_var_names=("PATH",), paths_option_name="[golang].go_search_paths", + extra_env_var_names=(), bin_relpath=golang_subsystem.asdf_bin_relpath, ), ) + env = await Get(EnvironmentVars, EnvironmentVarsRequest(("PATH",))) + return GoBootstrap( raw_go_search_paths=raw_go_search_paths, - environment=result.env, asdf_standard_tool_paths=result.standard_tool_paths, asdf_local_tool_paths=result.local_tool_paths, + _path=env.get("PATH", None), ) diff --git a/src/python/pants/backend/go/util_rules/sdk.py b/src/python/pants/backend/go/util_rules/sdk.py index c1362bb0e53..6bbec5fb4f9 100644 --- a/src/python/pants/backend/go/util_rules/sdk.py +++ b/src/python/pants/backend/go/util_rules/sdk.py @@ -104,14 +104,14 @@ async def setup_go_sdk_process( request: GoSdkProcess, go_sdk_run: GoSdkRunSetup, bash: BashBinary, - golang_subsystem: GolangSubsystem, + golang_env_aware: GolangSubsystem.EnvironmentAware, goroot: GoRoot, ) -> Process: input_digest, env_vars = await MultiGet( Get(Digest, MergeDigests([go_sdk_run.digest, request.input_digest])), Get( EnvironmentVars, - EnvironmentVarsRequest(golang_subsystem.env_vars_to_pass_to_subprocesses), + EnvironmentVarsRequest(golang_env_aware.env_vars_to_pass_to_subprocesses), ), ) maybe_replace_sandbox_root_env = ( From 4640a6c5e1e2c94295a3ff52610a62de99c62f93 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Thu, 29 Sep 2022 14:13:21 -0700 Subject: [PATCH 2/2] Make the go bootstrap more amenable to caching when asdf is not used # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- .../backend/go/util_rules/go_bootstrap.py | 108 ++++++++++-------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/src/python/pants/backend/go/util_rules/go_bootstrap.py b/src/python/pants/backend/go/util_rules/go_bootstrap.py index 632d0d70b69..fafab26f154 100644 --- a/src/python/pants/backend/go/util_rules/go_bootstrap.py +++ b/src/python/pants/backend/go/util_rules/go_bootstrap.py @@ -5,74 +5,84 @@ import logging import os from dataclasses import dataclass +from typing import Iterable from pants.backend.go.subsystems.golang import GolangSubsystem from pants.core.util_rules import asdf from pants.core.util_rules.asdf import AsdfToolPathsRequest, AsdfToolPathsResult from pants.engine.env_vars import EnvironmentVars, EnvironmentVarsRequest from pants.engine.internals.selectors import Get -from pants.engine.rules import collect_rules, rule +from pants.engine.rules import collect_rules, rule, rule_helper logger = logging.getLogger(__name__) @dataclass(frozen=True) class GoBootstrap: - raw_go_search_paths: tuple[str, ...] - asdf_standard_tool_paths: tuple[str, ...] - asdf_local_tool_paths: tuple[str, ...] - _path: str | None - - @property - def go_search_paths(self) -> tuple[str, ...]: - special_strings = { - "": lambda: self.environment_paths, - "": lambda: self.asdf_standard_tool_paths, - "": lambda: self.asdf_local_tool_paths, - } - expanded: list[str] = [] - for s in self.raw_go_search_paths: - if s in special_strings: - special_paths = special_strings[s]() - expanded.extend(special_paths) - else: - expanded.append(s) - return tuple(expanded) - - @property - def environment_paths(self) -> list[str]: - """Returns a list of paths specified by the PATH env var.""" - if self._path: - return self._path.split(os.pathsep) - return [] + go_search_paths: tuple[str, ...] + + +@rule_helper +async def _go_search_paths( + golang_subsystem: GolangSubsystem, paths: Iterable[str] +) -> tuple[str, ...]: + + resolve_standard, resolve_local = "" in paths, "" in paths + + if resolve_standard or resolve_local: + # AsdfToolPathsResult is uncacheable, so only request it if absolutely necessary. + asdf_result = await Get( + AsdfToolPathsResult, + AsdfToolPathsRequest( + tool_name=golang_subsystem.asdf_tool_name, + tool_description="Go distribution", + resolve_standard=resolve_standard, + resolve_local=resolve_local, + paths_option_name="[golang].go_search_paths", + extra_env_var_names=(), + bin_relpath=golang_subsystem.asdf_bin_relpath, + ), + ) + asdf_standard_tool_paths = asdf_result.standard_tool_paths + asdf_local_tool_paths = asdf_result.local_tool_paths + else: + asdf_standard_tool_paths = () + asdf_local_tool_paths = () + + special_strings = { + "": lambda: asdf_standard_tool_paths, + "": lambda: asdf_local_tool_paths, + } + + expanded: list[str] = [] + for s in paths: + if s == "": + expanded.extend(await _environment_paths()) + elif s in special_strings: + special_paths = special_strings[s]() + expanded.extend(special_paths) + else: + expanded.append(s) + return tuple(expanded) + + +@rule_helper +async def _environment_paths() -> list[str]: + """Returns a list of paths specified by the PATH env var.""" + env = await Get(EnvironmentVars, EnvironmentVarsRequest(("PATH",))) + path = env.get("PATH") + if path: + return path.split(os.pathsep) + return [] @rule async def resolve_go_bootstrap( golang_subsystem: GolangSubsystem, golang_env_aware: GolangSubsystem.EnvironmentAware ) -> GoBootstrap: - raw_go_search_paths = golang_env_aware.raw_go_search_paths - result = await Get( - AsdfToolPathsResult, - AsdfToolPathsRequest( - tool_name=golang_subsystem.asdf_tool_name, - tool_description="Go distribution", - resolve_standard="" in raw_go_search_paths, - resolve_local="" in raw_go_search_paths, - paths_option_name="[golang].go_search_paths", - extra_env_var_names=(), - bin_relpath=golang_subsystem.asdf_bin_relpath, - ), - ) + paths = await _go_search_paths(golang_subsystem, golang_env_aware.raw_go_search_paths) - env = await Get(EnvironmentVars, EnvironmentVarsRequest(("PATH",))) - - return GoBootstrap( - raw_go_search_paths=raw_go_search_paths, - asdf_standard_tool_paths=result.standard_tool_paths, - asdf_local_tool_paths=result.local_tool_paths, - _path=env.get("PATH", None), - ) + return GoBootstrap(go_search_paths=paths) def compatible_go_version(*, compiler_version: str, target_version: str) -> bool: