Skip to content

Commit

Permalink
javascript: Allow nodejs-backed tools to point to a resolve and/or pa…
Browse files Browse the repository at this point in the history
…ckage manager (pantsbuild#19040)

Features:
- Enable the use of nodejs resolves(~=lockfiles) for the prettier,
spectral and pyright subsystems/tools. This is sort of equivalent to the
"user lockfiles for tools" in python
- Enable users to use a different package manager to execute the tool.
Mostly useful to utilize pnpm CAS.

Breaking plugin change that might be worth mentioning:
 - Rename `NpxToolBase` to `NodeJSToolBase`
- Removed `NodeToolProcess.npx`. - I think the package manager agnostic
execute interface is better to use.

Fixes pantsbuild#17615

Preparatory for a typescript backend, to implement a check goal using
the user resolve `tsc` (compiler) version.
  • Loading branch information
tobni authored May 24, 2023
1 parent 8c9555a commit 301e08f
Show file tree
Hide file tree
Showing 21 changed files with 1,740 additions and 187 deletions.
2 changes: 1 addition & 1 deletion src/python/pants/backend/javascript/goals/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ python_tests(
# The package.json files are inlined in ./test_integration_tests.py
overrides={
"test_integration_test.py": dict(
dependencies=["./jest_resources", "./mocha_resources"], timeout=120
dependencies=["./jest_resources", "./mocha_resources"], timeout=240
),
},
)
17 changes: 4 additions & 13 deletions src/python/pants/backend/javascript/goals/lockfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
NodeJsProjectEnvironmentProcess,
)
from pants.backend.javascript.package_json import PackageJsonTarget
from pants.backend.javascript.subsystems.nodejs import UserChosenNodeJSResolveAliases
from pants.backend.javascript.resolve import NodeJSProjectResolves, UserChosenNodeJSResolveAliases
from pants.core.goals.generate_lockfiles import (
GenerateLockfile,
GenerateLockfileResult,
Expand Down Expand Up @@ -92,24 +92,15 @@ async def determine_package_json_user_resolves(

@rule
async def setup_user_lockfile_requests(
resolves: NodeJSProjectResolves,
requested: RequestedPackageJsonUserResolveNames,
all_projects: AllNodeJSProjects,
user_chosen_resolves: UserChosenNodeJSResolveAliases,
) -> UserGenerateLockfiles:
def get_name(project: NodeJSProject) -> str:
return user_chosen_resolves.get(
os.path.join(project.root_dir, project.lockfile_name), project.default_resolve_name
)

projects_by_name = {get_name(project): project for project in all_projects}
return UserGenerateLockfiles(
GeneratePackageLockJsonFile(
resolve_name=name,
lockfile_dest=os.path.join(
projects_by_name[name].root_dir, projects_by_name[name].lockfile_name
),
lockfile_dest=os.path.join(resolves[name].root_dir, resolves[name].lockfile_name),
diff=False,
project=projects_by_name[name],
project=resolves[name],
)
for name in requested
)
Expand Down
2 changes: 1 addition & 1 deletion src/python/pants/backend/javascript/goals/lockfile_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
PackageJsonForGlobs,
PackageJsonTarget,
)
from pants.backend.javascript.subsystems.nodejs import UserChosenNodeJSResolveAliases
from pants.backend.javascript.resolve import UserChosenNodeJSResolveAliases
from pants.core.goals.generate_lockfiles import (
GenerateLockfileResult,
KnownUserResolveNames,
Expand Down
15 changes: 7 additions & 8 deletions src/python/pants/backend/javascript/lint/prettier/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
from __future__ import annotations

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

from pants.backend.javascript.lint.prettier.subsystem import Prettier
from pants.backend.javascript.subsystems.nodejs import NodeJSToolProcess
from pants.backend.javascript.subsystems import nodejs_tool
from pants.backend.javascript.subsystems.nodejs_tool import NodeJSToolRequest
from pants.backend.javascript.target_types import JSSourceField
from pants.core.goals.fmt import FmtResult, FmtTargetsRequest
from pants.core.util_rules.config_files import ConfigFiles, ConfigFilesRequest
Expand Down Expand Up @@ -59,13 +61,9 @@ async def prettier_fmt(request: PrettierFmtRequest.Batch, prettier: Prettier) ->

result = await Get(
ProcessResult,
NodeJSToolProcess,
NodeJSToolProcess.npx(
npm_package=prettier.version,
args=(
"--write",
*request.files,
),
NodeJSToolRequest,
prettier.request(
args=("--write", *(os.path.join("{chroot}", file) for file in request.files)),
input_digest=input_digest,
output_files=request.files,
description=f"Run Prettier on {pluralize(len(request.files), 'file')}.",
Expand All @@ -78,5 +76,6 @@ async def prettier_fmt(request: PrettierFmtRequest.Batch, prettier: Prettier) ->
def rules() -> Iterable[Rule | UnionRule]:
return (
*collect_rules(),
*nodejs_tool.rules(),
*PrettierFmtRequest.rules(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
import os
from typing import Iterable

from pants.backend.javascript.subsystems.npx_tool import NpxToolBase
from pants.backend.javascript.subsystems.nodejs_tool import NodeJSToolBase
from pants.core.util_rules.config_files import ConfigFilesRequest
from pants.option.option_types import ArgsListOption, SkipOption
from pants.util.strutil import help_text


class Prettier(NpxToolBase):
class Prettier(NodeJSToolBase):
options_scope = "prettier"
name = "Prettier"
help = help_text(
Expand Down
43 changes: 43 additions & 0 deletions src/python/pants/backend/javascript/resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@
from pants.backend.javascript import nodejs_project
from pants.backend.javascript.nodejs_project import AllNodeJSProjects, NodeJSProject
from pants.backend.javascript.package_json import (
FirstPartyNodePackageTargets,
NodePackageNameField,
OwningNodePackage,
OwningNodePackageRequest,
PackageJsonSourceField,
)
from pants.backend.javascript.subsystems.nodejs import NodeJS
from pants.build_graph.address import Address
from pants.engine.fs import PathGlobs
from pants.engine.internals.selectors import Get
from pants.engine.rules import Rule, collect_rules, rule
from pants.engine.target import Target, WrappedTarget, WrappedTargetRequest
from pants.engine.unions import UnionRule
from pants.util.frozendict import FrozenDict
from pants.util.logging import LogLevel


@dataclass(frozen=True)
Expand Down Expand Up @@ -68,5 +72,44 @@ async def resolve_for_package(
return ChosenNodeResolve(project)


class NodeJSProjectResolves(FrozenDict[str, NodeJSProject]):
pass


class UserChosenNodeJSResolveAliases(FrozenDict[str, str]):
pass


@rule
async def resolve_to_projects(
all_projects: AllNodeJSProjects, user_chosen_resolves: UserChosenNodeJSResolveAliases
) -> NodeJSProjectResolves:
def get_name(project: NodeJSProject) -> str:
return user_chosen_resolves.get(
os.path.join(project.root_dir, project.lockfile_name), project.default_resolve_name
)

return NodeJSProjectResolves((get_name(project), project) for project in all_projects)


class FirstPartyNodePackageResolves(FrozenDict[str, Target]):
pass


@rule
async def resolve_to_first_party_node_package(
resolves: NodeJSProjectResolves, all_first_party: FirstPartyNodePackageTargets
) -> FirstPartyNodePackageResolves:
by_dir = {first_party.residence_dir: first_party for first_party in all_first_party}
return FirstPartyNodePackageResolves(
(resolve, by_dir[project.root_dir]) for resolve, project in resolves.items()
)


@rule(level=LogLevel.DEBUG)
async def user_chosen_resolve_aliases(nodejs: NodeJS) -> UserChosenNodeJSResolveAliases:
return UserChosenNodeJSResolveAliases((value, key) for key, value in nodejs.resolves.items())


def rules() -> Iterable[Rule | UnionRule]:
return [*collect_rules(), *nodejs_project.rules()]
3 changes: 3 additions & 0 deletions src/python/pants/backend/javascript/subsystems/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ python_tests(
name="tests",
overrides={
"nodejs_test.py": {"timeout": 240},
"nodejs_tool_test.py": {"timeout": 120, "dependencies": [":cowsay_lockfiles"]},
},
)

resources(name="cowsay_lockfiles", sources=["package-lock.json", "pnpm-lock.yaml", "yarn.lock"])
29 changes: 0 additions & 29 deletions src/python/pants/backend/javascript/subsystems/nodejs.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,6 @@ def corepack_env_vars(self) -> tuple[str, ...]:
return tuple(sorted(set(self._corepack_env_vars)))


class UserChosenNodeJSResolveAliases(FrozenDict[str, str]):
pass


@rule(level=LogLevel.DEBUG)
async def user_chosen_resolve_aliases(nodejs: NodeJS) -> UserChosenNodeJSResolveAliases:
return UserChosenNodeJSResolveAliases((value, key) for key, value in nodejs.resolves.items())


@dataclass(frozen=True)
class NodeJSToolProcess:
"""A request for a tool installed with NodeJS."""
Expand Down Expand Up @@ -278,26 +269,6 @@ def npm(
project_digest=project_digest,
)

@classmethod
def npx(
cls,
args: Iterable[str],
npm_package: str,
description: str,
level: LogLevel = LogLevel.INFO,
input_digest: Digest = EMPTY_DIGEST,
output_files: tuple[str, ...] = (),
) -> NodeJSToolProcess:
return cls(
tool="npx",
tool_version=None,
args=("--yes", npm_package, *args),
description=description,
level=level,
input_digest=input_digest,
output_files=output_files,
)


@dataclass(frozen=True)
class NodeJSBinaries:
Expand Down
32 changes: 0 additions & 32 deletions src/python/pants/backend/javascript/subsystems/nodejs_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,38 +59,6 @@ def rule_runner() -> RuleRunner:
return rule_runner


def test_npx_process(rule_runner: RuleRunner):
rule_runner.set_options(["--nodejs-package-managers={'npm': '8.5.5'}"], env_inherit={"PATH"})
result = rule_runner.request(
ProcessResult,
[
nodejs.NodeJSToolProcess.npx(
npm_package="",
args=("--version",),
description="Testing NpxProcess",
)
],
)

assert result.stdout.strip() == b"8.5.5"


def test_npx_process_with_different_version(rule_runner: RuleRunner):
rule_runner.set_options(["--nodejs-package-managers={'npm': '7.20.0'}"], env_inherit={"PATH"})
result = rule_runner.request(
ProcessResult,
[
nodejs.NodeJSToolProcess.npx(
npm_package="",
args=("--version",),
description="Testing NpxProcess",
)
],
)

assert result.stdout.strip() == b"7.20.0"


def test_npm_process(rule_runner: RuleRunner):
rule_runner.set_options(["--nodejs-package-managers={'npm': '8.5.5'}"], env_inherit={"PATH"})
result = rule_runner.request(
Expand Down
Loading

0 comments on commit 301e08f

Please sign in to comment.