• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/python/pants/backend/javascript/package/rules.py
1
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
UNCOV
3
from __future__ import annotations
×
4

UNCOV
5
import re
×
UNCOV
6
from collections.abc import Iterable
×
UNCOV
7
from dataclasses import dataclass
×
UNCOV
8
from typing import ClassVar
×
9

UNCOV
10
from pants.backend.javascript import install_node_package
×
UNCOV
11
from pants.backend.javascript.install_node_package import (
×
12
    InstalledNodePackageRequest,
13
    add_sources_to_installed_node_package,
14
)
UNCOV
15
from pants.backend.javascript.nodejs_project_environment import NodeJsProjectEnvironmentProcess
×
UNCOV
16
from pants.backend.javascript.package_json import (
×
17
    NodeBuildScript,
18
    NodeBuildScriptEntryPointField,
19
    NodeBuildScriptExtraCaches,
20
    NodeBuildScriptExtraEnvVarsField,
21
    NodeBuildScriptOutputDirectoriesField,
22
    NodeBuildScriptOutputFilesField,
23
    NodeBuildScriptSourcesField,
24
    NodePackageNameField,
25
    NodePackageVersionField,
26
    NPMDistributionTarget,
27
    PackageJsonSourceField,
28
)
UNCOV
29
from pants.build_graph.address import Address
×
UNCOV
30
from pants.core.goals.package import (
×
31
    BuiltPackage,
32
    BuiltPackageArtifact,
33
    OutputPathField,
34
    PackageFieldSet,
35
)
UNCOV
36
from pants.core.target_types import ResourceSourceField
×
UNCOV
37
from pants.core.util_rules.env_vars import environment_vars_subset
×
UNCOV
38
from pants.engine.env_vars import EnvironmentVarsRequest
×
UNCOV
39
from pants.engine.internals.native_engine import AddPrefix
×
UNCOV
40
from pants.engine.intrinsics import add_prefix, digest_to_snapshot
×
UNCOV
41
from pants.engine.process import ProcessResult, fallible_to_exec_result_or_raise
×
UNCOV
42
from pants.engine.rules import Rule, collect_rules, implicitly, rule
×
UNCOV
43
from pants.engine.target import GeneratedSources, GenerateSourcesRequest
×
UNCOV
44
from pants.engine.unions import UnionRule
×
UNCOV
45
from pants.util.frozendict import FrozenDict
×
UNCOV
46
from pants.util.logging import LogLevel
×
UNCOV
47
from pants.util.strutil import softwrap
×
48

49

UNCOV
50
@dataclass(frozen=True)
×
UNCOV
51
class NodePackageTarFieldSet(PackageFieldSet):
×
UNCOV
52
    required_fields = (PackageJsonSourceField, OutputPathField)
×
UNCOV
53
    source: PackageJsonSourceField
×
UNCOV
54
    output_path: OutputPathField
×
55

56

UNCOV
57
@dataclass(frozen=True)
×
UNCOV
58
class NodeBuildScriptPackageFieldSet(PackageFieldSet):
×
UNCOV
59
    required_fields = (
×
60
        NodeBuildScriptSourcesField,
61
        OutputPathField,
62
        NodeBuildScriptEntryPointField,
63
        NodeBuildScriptOutputFilesField,
64
        NodeBuildScriptOutputDirectoriesField,
65
        NodeBuildScriptExtraCaches,
66
    )
UNCOV
67
    source: NodeBuildScriptSourcesField
×
UNCOV
68
    output_path: OutputPathField
×
UNCOV
69
    script_name: NodeBuildScriptEntryPointField
×
UNCOV
70
    output_directories: NodeBuildScriptOutputDirectoriesField
×
UNCOV
71
    output_files: NodeBuildScriptOutputFilesField
×
UNCOV
72
    extra_caches: NodeBuildScriptExtraCaches
×
UNCOV
73
    extra_env_vars: NodeBuildScriptExtraEnvVarsField
×
74

75

UNCOV
76
@dataclass(frozen=True)
×
UNCOV
77
class GenerateResourcesFromNodeBuildScriptRequest(GenerateSourcesRequest):
×
UNCOV
78
    input = NodeBuildScriptSourcesField
×
UNCOV
79
    output = ResourceSourceField
×
80

UNCOV
81
    exportable: ClassVar[bool] = True
×
82

83

UNCOV
84
@rule
×
UNCOV
85
async def pack_node_package_into_tgz_for_publication(
×
86
    field_set: NodePackageTarFieldSet,
87
) -> BuiltPackage:
88
    installation = await add_sources_to_installed_node_package(
×
89
        InstalledNodePackageRequest(field_set.address)
90
    )
91
    node_package = installation.project_env.ensure_target()
×
92
    name = node_package.get(NodePackageNameField).value
×
93
    version = node_package.get(NodePackageVersionField).value
×
94
    if version is None:
×
95
        raise ValueError(
×
96
            f"{field_set.source.file_path}#version must be set in order to package a {NPMDistributionTarget.alias}."
97
        )
98
    archive_file = installation.project_env.project.pack_archive_format.format(name, version)
×
99
    result = await fallible_to_exec_result_or_raise(
×
100
        **implicitly(
101
            NodeJsProjectEnvironmentProcess(
102
                installation.project_env,
103
                args=("pack",),
104
                description=f"Packaging .tgz archive for {name}@{version}",
105
                input_digest=installation.digest,
106
                output_files=(installation.join_relative_workspace_directory(archive_file),),
107
                level=LogLevel.INFO,
108
            )
109
        )
110
    )
111
    if field_set.output_path.value:
×
112
        output_path = field_set.output_path.value_or_default(file_ending=None)
×
113
        digest = await add_prefix(AddPrefix(result.output_digest, output_path))
×
114
    else:
115
        digest = result.output_digest
×
116

117
    return BuiltPackage(
×
118
        digest, (BuiltPackageArtifact(archive_file, tuple(result.stderr.decode().splitlines())),)
119
    )
120

121

UNCOV
122
_NOT_ALPHANUMERIC = re.compile("[^0-9a-zA-Z]+")
×
123

124

UNCOV
125
@dataclass(frozen=True)
×
UNCOV
126
class NodeBuildScriptResult:
×
UNCOV
127
    process: ProcessResult
×
UNCOV
128
    project_directory: str
×
129

130

UNCOV
131
@dataclass(frozen=True)
×
UNCOV
132
class NodeBuildScriptRequest:
×
UNCOV
133
    address: Address
×
UNCOV
134
    output_files: tuple[str, ...]
×
UNCOV
135
    output_directories: tuple[str, ...]
×
UNCOV
136
    script_name: str
×
UNCOV
137
    extra_caches: tuple[str, ...]
×
UNCOV
138
    extra_env_vars: tuple[str, ...]
×
139

UNCOV
140
    def __post_init__(self) -> None:
×
141
        if not (self.output_directories or self.output_files):
×
142
            raise ValueError(
×
143
                softwrap(
144
                    f"""
145
                    Neither the {NodeBuildScriptOutputDirectoriesField.alias} nor the
146
                    {NodeBuildScriptOutputFilesField.alias} field was provided.
147

148
                    One of the fields have to be set, or else the `{NodeBuildScript.alias}`
149
                    output will not be captured for further use in the build.
150
                    """
151
                )
152
            )
153

UNCOV
154
    @classmethod
×
UNCOV
155
    def from_generate_request(
×
156
        cls, req: GenerateResourcesFromNodeBuildScriptRequest
157
    ) -> NodeBuildScriptRequest:
158
        return cls(
×
159
            address=req.protocol_target.address,
160
            output_files=req.protocol_target[NodeBuildScriptOutputFilesField].value or (),
161
            output_directories=req.protocol_target[NodeBuildScriptOutputDirectoriesField].value
162
            or (),
163
            script_name=req.protocol_target[NodeBuildScriptEntryPointField].value,
164
            extra_caches=req.protocol_target[NodeBuildScriptExtraCaches].value or (),
165
            extra_env_vars=req.protocol_target[NodeBuildScriptExtraEnvVarsField].value or (),
166
        )
167

UNCOV
168
    @classmethod
×
UNCOV
169
    def from_package_request(cls, req: NodeBuildScriptPackageFieldSet) -> NodeBuildScriptRequest:
×
170
        return cls(
×
171
            address=req.address,
172
            output_files=req.output_files.value or (),
173
            output_directories=req.output_directories.value or (),
174
            script_name=req.script_name.value,
175
            extra_caches=req.extra_caches.value or (),
176
            extra_env_vars=req.extra_env_vars.value or (),
177
        )
178

UNCOV
179
    def get_paths(self) -> Iterable[str]:
×
180
        yield from self.output_directories
×
181
        yield from self.output_files
×
182

183

UNCOV
184
@rule
×
UNCOV
185
async def run_node_build_script(req: NodeBuildScriptRequest) -> NodeBuildScriptResult:
×
186
    installation = await add_sources_to_installed_node_package(
×
187
        InstalledNodePackageRequest(req.address)
188
    )
189
    output_files = req.output_files
×
190
    output_dirs = req.output_directories
×
191
    script_name = req.script_name
×
192
    extra_caches = req.extra_caches
×
193
    extra_env_vars = req.extra_env_vars
×
194

195
    def cache_name(cache_path: str) -> str:
×
196
        parts = (installation.project_env.package_dir(), script_name, cache_path)
×
197
        return "_".join(_NOT_ALPHANUMERIC.sub("_", part) for part in parts if part)
×
198

199
    args = ("run", script_name)
×
200
    target_env_vars = await environment_vars_subset(
×
201
        EnvironmentVarsRequest(extra_env_vars), **implicitly()
202
    )
203
    result = await fallible_to_exec_result_or_raise(
×
204
        **implicitly(
205
            NodeJsProjectEnvironmentProcess(
206
                installation.project_env,
207
                args=filter(None, args),
208
                description=f"Running node build script '{script_name}'.",
209
                input_digest=installation.digest,
210
                output_files=tuple(
211
                    installation.join_relative_workspace_directory(file)
212
                    for file in output_files or ()
213
                ),
214
                output_directories=tuple(
215
                    installation.join_relative_workspace_directory(directory)
216
                    for directory in output_dirs or ()
217
                ),
218
                level=LogLevel.INFO,
219
                per_package_caches=FrozenDict(
220
                    {cache_name(extra_cache): extra_cache for extra_cache in extra_caches or ()}
221
                ),
222
                extra_env=target_env_vars,
223
            )
224
        )
225
    )
226

227
    return NodeBuildScriptResult(result, installation.project_dir)
×
228

229

UNCOV
230
@rule
×
UNCOV
231
async def generate_resources_from_node_build_script(
×
232
    req: GenerateResourcesFromNodeBuildScriptRequest,
233
) -> GeneratedSources:
234
    result = await run_node_build_script(NodeBuildScriptRequest.from_generate_request(req))
×
235
    return GeneratedSources(
×
236
        await digest_to_snapshot(
237
            **implicitly(AddPrefix(result.process.output_digest, result.project_directory))
238
        )
239
    )
240

241

UNCOV
242
@rule
×
UNCOV
243
async def generate_package_artifact_from_node_build_script(
×
244
    req: NodeBuildScriptPackageFieldSet,
245
) -> BuiltPackage:
246
    request = NodeBuildScriptRequest.from_package_request(req)
×
247
    result = await run_node_build_script(request)
×
248
    if req.output_path.value:
×
249
        output_path = req.output_path.value_or_default(file_ending=None)
×
250
        digest = await add_prefix(AddPrefix(result.process.output_digest, output_path))
×
251
    else:
252
        digest = result.process.output_digest
×
253
    artifacts = tuple(BuiltPackageArtifact(path) for path in request.get_paths())
×
254
    return BuiltPackage(digest, artifacts)
×
255

256

UNCOV
257
def rules() -> Iterable[Rule | UnionRule]:
×
UNCOV
258
    return [
×
259
        *collect_rules(),
260
        *install_node_package.rules(),
261
        UnionRule(PackageFieldSet, NodePackageTarFieldSet),
262
        UnionRule(PackageFieldSet, NodeBuildScriptPackageFieldSet),
263
        UnionRule(GenerateSourcesRequest, GenerateResourcesFromNodeBuildScriptRequest),
264
    ]
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc