Skip to content

Commit

Permalink
Refactor common "bootstrap" logic to prepare for internal re-use (pan…
Browse files Browse the repository at this point in the history
…tsbuild#18417)

There was some overlap between `golang` and `python-bootstrap` search
path handling, and I did not want to repeat it for the `nodejs`
subsystem, which will also have `asdf` support, for example. Preparatory
for pantsbuild#16959.

---------

Co-authored-by: Christopher Neugebauer <[email protected]>
  • Loading branch information
tobni and chrisjrn authored Mar 7, 2023
1 parent 81aeb2f commit 7964e8d
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 290 deletions.
8 changes: 4 additions & 4 deletions src/python/pants/backend/go/subsystems/golang.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import os

from pants.core.util_rules.asdf import AsdfPathString
from pants.option.option_types import BoolOption, StrListOption, StrOption
from pants.option.subsystem import Subsystem
from pants.util.memo import memoized_property
Expand All @@ -29,7 +30,7 @@ class EnvironmentAware(Subsystem.EnvironmentAware):
_go_search_paths = StrListOption(
default=["<PATH>"],
help=softwrap(
"""
f"""
A list of paths to search for Go.
Specify absolute paths to directories with the `go` binary, e.g. `/usr/bin`.
Expand All @@ -38,9 +39,8 @@ class EnvironmentAware(Subsystem.EnvironmentAware):
The following special strings are supported:
* `<PATH>`, the contents of the PATH environment variable
* `<ASDF>`, all Go versions currently configured by ASDF \
`(asdf shell, ${HOME}/.tool-versions)`, with a fallback to all installed versions
* `<ASDF_LOCAL>`, the ASDF interpreter with the version in BUILD_ROOT/.tool-versions
* `{AsdfPathString.STANDARD}`, {AsdfPathString.STANDARD.description("Go")}
* `{AsdfPathString.LOCAL}`, {AsdfPathString.LOCAL.description("binary")}
"""
),
)
Expand Down
116 changes: 33 additions & 83 deletions src/python/pants/backend/go/util_rules/go_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
from __future__ import annotations

import logging
import os
from dataclasses import dataclass
from typing import Iterable
from typing import Collection

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.core.util_rules.environments import EnvironmentTarget, LocalEnvironmentTarget
from pants.engine.env_vars import EnvironmentVars, EnvironmentVarsRequest
from pants.core.util_rules import asdf, search_paths
from pants.core.util_rules.asdf import AsdfPathString, AsdfToolPathsResult
from pants.core.util_rules.environments import EnvironmentTarget
from pants.core.util_rules.search_paths import ValidatedSearchPaths, ValidateSearchPathsRequest
from pants.engine.env_vars import PathEnvironmentVariable
from pants.engine.internals.selectors import Get
from pants.engine.rules import collect_rules, rule
from pants.util.strutil import softwrap
from pants.util.ordered_set import FrozenOrderedSet

logger = logging.getLogger(__name__)

Expand All @@ -25,101 +25,50 @@ class GoBootstrap:


async def _go_search_paths(
env_tgt: EnvironmentTarget, golang_subsystem: GolangSubsystem, paths: Iterable[str]
env_tgt: EnvironmentTarget, golang_subsystem: GolangSubsystem, paths: Collection[str]
) -> tuple[str, ...]:

resolve_standard, resolve_local = "<ASDF>" in paths, "<ASDF_LOCAL>" in paths

if resolve_standard or resolve_local:
# AsdfToolPathsResult is uncacheable, so only request it if absolutely necessary.
asdf_result = await Get(
AsdfToolPathsResult,
AsdfToolPathsRequest(
env_tgt=env_tgt,
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",
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 = ()

asdf_result = await AsdfToolPathsResult.get_un_cachable_search_paths(
paths,
env_tgt=env_tgt,
tool_name=golang_subsystem.asdf_tool_name,
tool_description="Go distribution",
paths_option_name="[golang].go_search_paths",
bin_relpath=golang_subsystem.asdf_bin_relpath,
)
special_strings = {
"<ASDF>": lambda: asdf_standard_tool_paths,
"<ASDF_LOCAL>": lambda: asdf_local_tool_paths,
AsdfPathString.STANDARD.value: asdf_result.standard_tool_paths,
AsdfPathString.LOCAL.value: asdf_result.local_tool_paths,
}

path_variables = await Get(PathEnvironmentVariable, {})
expanded: list[str] = []
for s in paths:
if s == "<PATH>":
expanded.extend(await _environment_paths())
expanded.extend(path_variables)
elif s in special_strings:
special_paths = special_strings[s]()
special_paths = special_strings[s]
expanded.extend(special_paths)
else:
expanded.append(s)
return tuple(expanded)


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 []


def _error_if_not_compatible_with_asdf(
env_tgt: EnvironmentTarget,
_search_paths: Iterable[str],
) -> None:
"""Raises an exception if any special search path strings any are invalid for the environment.
If the environment is non-local and there are invalid tokens for those environments, raise
`ValueError`.
"""

env = env_tgt.val

if env is None or isinstance(env, LocalEnvironmentTarget):
return

not_allowed = {"<ASDF>", "<ASDF_LOCAL>"}

any_not_allowed = set(_search_paths) & not_allowed
if any_not_allowed:
env_type = type(env)
raise ValueError(
softwrap(
f"`[python-bootstrap].search_paths` is configured to use local Go discovery "
f"tools, which do not work in {env_type.__name__} runtime environments. To fix "
f"this, set the value of `golang_go_search_paths` in the `{env.alias}` "
f"defined at `{env.address}` to contain only hardcoded paths or the `<PATH>` "
"special string."
)
)

return


@rule
async def resolve_go_bootstrap(
golang_subsystem: GolangSubsystem, golang_env_aware: GolangSubsystem.EnvironmentAware
) -> GoBootstrap:

_error_if_not_compatible_with_asdf(
golang_env_aware.env_tgt, golang_env_aware.raw_go_search_paths
)
paths = await _go_search_paths(
golang_env_aware.env_tgt, golang_subsystem, golang_env_aware.raw_go_search_paths
search_paths = await Get(
ValidatedSearchPaths,
ValidateSearchPathsRequest(
env_tgt=golang_env_aware.env_tgt,
search_paths=tuple(golang_env_aware.raw_go_search_paths),
option_origin=f"[{GolangSubsystem.options_scope}].go_search_paths",
environment_key="golang_go_search_paths",
is_default=golang_env_aware._is_default("_go_search_paths"),
local_only=FrozenOrderedSet((AsdfPathString.STANDARD, AsdfPathString.LOCAL)),
),
)
paths = await _go_search_paths(golang_env_aware.env_tgt, golang_subsystem, search_paths)

return GoBootstrap(go_search_paths=paths)

Expand All @@ -146,4 +95,5 @@ def rules():
return (
*collect_rules(),
*asdf.rules(),
*search_paths.rules(),
)
Loading

0 comments on commit 7964e8d

Please sign in to comment.