Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javascript: Add an explicit npm_distribution target instead of coupling to node_package #18925

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/python/pants/backend/javascript/install_node_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from pants.engine.rules import Rule, collect_rules, rule
from pants.engine.target import (
SourcesField,
Target,
TransitiveTargets,
TransitiveTargetsRequest,
targets_with_sources_types,
Expand All @@ -54,6 +55,10 @@ def project_dir(self) -> str:
def join_relative_workspace_directory(self, path: str) -> str:
return os.path.join(self.project_env.relative_workspace_directory(), path)

@property
def target(self) -> Target:
return self.project_env.ensure_target()


@dataclass(frozen=True)
class InstalledNodePackageWithSource(InstalledNodePackage):
Expand Down Expand Up @@ -127,7 +132,9 @@ async def add_sources_to_installed_node_package(
req: InstalledNodePackageRequest,
) -> InstalledNodePackageWithSource:
installation = await Get(InstalledNodePackage, InstalledNodePackageRequest, req)
transitive_tgts = await Get(TransitiveTargets, TransitiveTargetsRequest([req.address]))
transitive_tgts = await Get(
TransitiveTargets, TransitiveTargetsRequest([installation.target.address])
)

source_files = await _get_relevant_source_files(
(tgt[SourcesField] for tgt in transitive_tgts.dependencies if tgt.has_field(SourcesField)),
Expand Down
27 changes: 19 additions & 8 deletions src/python/pants/backend/javascript/package/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
NodeBuildScriptSourcesField,
NodePackageNameField,
NodePackageVersionField,
NPMDistributionTarget,
PackageJsonSourceField,
)
from pants.build_graph.address import Address
Expand All @@ -44,10 +45,9 @@

@dataclass(frozen=True)
class NodePackageTarFieldSet(PackageFieldSet):
required_fields = (PackageJsonSourceField, NodePackageNameField, NodePackageVersionField)
required_fields = (PackageJsonSourceField, OutputPathField)
source: PackageJsonSourceField
name: NodePackageNameField
version: NodePackageVersionField
output_path: OutputPathField


@dataclass(frozen=True)
Expand Down Expand Up @@ -83,22 +83,33 @@ async def pack_node_package_into_tgz_for_publication(
installation = await Get(
InstalledNodePackageWithSource, InstalledNodePackageRequest(field_set.address)
)
archive_file = installation.project_env.project.pack_archive_format.format(
field_set.name.value, field_set.version.value
)
node_package = installation.project_env.ensure_target()
name = node_package.get(NodePackageNameField).value
version = node_package.get(NodePackageVersionField).value
if version is None:
raise ValueError(
f"{field_set.source.file_path}#version must be set in order to package a {NPMDistributionTarget.alias}."
)
archive_file = installation.project_env.project.pack_archive_format.format(name, version)
result = await Get(
ProcessResult,
NodeJsProjectEnvironmentProcess(
installation.project_env,
args=("pack",),
description=f"Packaging .tgz archive for {field_set.name.value}@{field_set.version.value}",
description=f"Packaging .tgz archive for {name}@{version}",
input_digest=installation.digest,
output_files=(installation.join_relative_workspace_directory(archive_file),),
level=LogLevel.INFO,
),
)
if field_set.output_path.value:
digest = await Get(Digest, AddPrefix(result.output_digest, field_set.output_path.value))
else:
digest = result.output_digest

return BuiltPackage(result.output_digest, (BuiltPackageArtifact(archive_file),))
return BuiltPackage(
digest, (BuiltPackageArtifact(archive_file, tuple(result.stderr.decode().splitlines())),)
)


_NOT_ALPHANUMERIC = re.compile("[^0-9a-zA-Z]+")
Expand Down
6 changes: 5 additions & 1 deletion src/python/pants/backend/javascript/package/rules_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
NodePackageTarFieldSet,
)
from pants.backend.javascript.package.rules import rules as package_rules
from pants.backend.javascript.package_json import NPMDistributionTarget
from pants.backend.javascript.target_types import JSSourcesGeneratorTarget, JSSourceTarget
from pants.build_graph.address import Address
from pants.core.goals.package import BuiltPackage
Expand Down Expand Up @@ -45,6 +46,7 @@ def rule_runner(package_manager: str) -> RuleRunner:
*package_json.target_types(),
JSSourceTarget,
JSSourcesGeneratorTarget,
NPMDistributionTarget,
FilesGeneratorTarget,
],
objects=dict(package_json.build_file_aliases().objects),
Expand All @@ -60,6 +62,8 @@ def test_creates_tar_for_package_json(rule_runner: RuleRunner, package_manager:
"""\
package_json(dependencies=[":readme"])
files(name="readme", sources=["*.md"])

npm_distribution(name="ham-dist")
"""
),
"src/js/package.json": json.dumps(
Expand All @@ -83,7 +87,7 @@ def test_creates_tar_for_package_json(rule_runner: RuleRunner, package_manager:
"src/js/lib/index.mjs": "",
}
)
tgt = rule_runner.get_target(Address("src/js", generated_name="ham"))
tgt = rule_runner.get_target(Address("src/js", target_name="ham-dist"))
result = rule_runner.request(BuiltPackage, [NodePackageTarFieldSet.create(tgt)])
rule_runner.write_digest(result.digest)

Expand Down
33 changes: 28 additions & 5 deletions src/python/pants/backend/javascript/package_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ class NodePackageVersionField(StringField):
This field should not be overridden; use the value from target generation.
"""
)
required = True
value: str
required = False
value: str | None


class NodeThirdPartyPackageVersionField(NodePackageVersionField):
Expand Down Expand Up @@ -320,6 +320,25 @@ class NodePackageTarget(Target):
)


class NPMDistributionTarget(Target):
alias = "npm_distribution"

help = help_text(
"""
A publishable npm registry distribution, typically a gzipped tarball
of the sources and any resources, but omitting the lockfile.

Generated using the projects package manager `pack` implementation.
"""
)

core_fields = (
*COMMON_TARGET_FIELDS,
PackageJsonSourceField,
OutputPathField,
)


class PackageJsonTarget(TargetGenerator):
alias = "package_json"
core_fields = (
Expand Down Expand Up @@ -595,7 +614,7 @@ def from_package_json(cls, pkg_json: PackageJson) -> PackageJsonScripts:
class PackageJson:
content: FrozenDict[str, Any]
name: str
version: str
version: str | None
snapshot: Snapshot
workspaces: tuple[str, ...] = ()
module: Literal["commonjs", "module"] | None = None
Expand Down Expand Up @@ -671,7 +690,11 @@ async def find_owning_package(request: OwningNodePackageRequest) -> OwningNodePa
),
)
package_json_tgts = sorted(
(tgt for tgt in candidate_targets if tgt.has_field(PackageJsonSourceField)),
(
tgt
for tgt in candidate_targets
if tgt.has_field(PackageJsonSourceField) and tgt.has_field(NodePackageNameField)
),
key=lambda tgt: tgt.address.spec_path,
reverse=True,
)
Expand All @@ -690,7 +713,7 @@ async def parse_package_json(content: FileContent) -> PackageJson:
return PackageJson(
content=parsed_package_json,
name=parsed_package_json["name"],
version=parsed_package_json["version"],
version=parsed_package_json.get("version"),
snapshot=await Get(Snapshot, PathGlobs([content.path])),
module=parsed_package_json.get("type"),
workspaces=tuple(parsed_package_json.get("workspaces", ())),
Expand Down
5 changes: 4 additions & 1 deletion src/python/pants/backend/javascript/resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pants.backend.javascript import nodejs_project
from pants.backend.javascript.nodejs_project import AllNodeJSProjects, NodeJSProject
from pants.backend.javascript.package_json import (
NodePackageNameField,
OwningNodePackage,
OwningNodePackageRequest,
PackageJsonSourceField,
Expand Down Expand Up @@ -48,7 +49,9 @@ async def _get_node_package_json_directory(req: RequestNodeResolve) -> str:
WrappedTargetRequest(req.address, description_of_origin="the `ChosenNodeResolve` rule"),
)
target: Target | None
if wrapped.target.has_field(PackageJsonSourceField):
if wrapped.target.has_field(PackageJsonSourceField) and wrapped.target.has_field(
NodePackageNameField
):
target = wrapped.target
else:
owning_pkg = await Get(OwningNodePackage, OwningNodePackageRequest(wrapped.target.address))
Expand Down