Skip to content
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: 9 additions & 0 deletions e2e/pnpm_repo_install/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ sh_test(
args = ["$(location @pnpm_v11//:pnpm)"],
data = ["@pnpm_v11//:pnpm"],
)

sh_test(
name = "pnpm_patch_test",
srcs = ["pnpm_patch_test.sh"],
data = [
"@bazel_tools//tools/bash/runfiles",
"@pnpm_patched//:pnpm",
],
)
8 changes: 7 additions & 1 deletion e2e/pnpm_repo_install/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ pnpm.pnpm(
pnpm_version = "11.0.4",
pnpm_version_integrity = "sha512-CjlxZQB6AU7VKRmmHl9GxIubyohATDA+yuzGP2Le9WOJjTxril1epYEes5jP4DqwXaGlzpY/Em1erUwC+TuDww==",
)
use_repo(pnpm, "pnpm", "pnpm_v10", "pnpm_v11")
pnpm.pnpm(
name = "pnpm_patched",
patches = ["//:patches/pnpm_hello.patch"],
pnpm_version = "9.15.9",
pnpm_version_integrity = "sha512-aARhQYk8ZvrQHAeSMRKOmvuJ74fiaR1p5NQO7iKJiClf1GghgbrlW1hBjDolO95lpQXsfF+UA+zlzDzTfc8lMQ==",
)
use_repo(pnpm, "pnpm", "pnpm_patched", "pnpm_v10", "pnpm_v11")
6 changes: 6 additions & 0 deletions e2e/pnpm_repo_install/patches/pnpm_hello.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
diff --git a/hello.txt b/hello.txt
new file mode 100644
--- /dev/null
+++ b/hello.txt
@@ -0,0 +1 @@
+hello
29 changes: 29 additions & 0 deletions e2e/pnpm_repo_install/pnpm_patch_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -o errexit -o nounset -o pipefail

# --- begin runfiles.bash initialization v3 ---
# Copy-pasted from the Bazel Bash runfiles library v3.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
# shellcheck disable=SC1090
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v3 ---

HELLO=$(rlocation "pnpm_patched/package/hello.txt")

if [ ! -f "${HELLO}" ]; then
echo "ERROR: expected hello.txt to exist at ${HELLO}"
exit 1
fi

CONTENT=$(cat "${HELLO}")
if [ "${CONTENT}" != "hello" ]; then
echo "ERROR: expected content 'hello', got '${CONTENT}'"
exit 1
fi

echo "PASS: pnpm patch applied successfully"
12 changes: 12 additions & 0 deletions npm/extensions.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,8 @@ def _pnpm_extension_impl(module_ctx):
pnpm_version = pnpm["version"],
integrity = pnpm["integrity"],
include_npm = pnpm["include_npm"],
patches = pnpm.get("patches", []),
patch_args = pnpm.get("patch_args", ["-p1"]),
)

kwargs = {}
Expand Down Expand Up @@ -402,6 +404,16 @@ pnpm = module_extension(
default = None,
),
"pnpm_version_integrity": attr.string(),
"patches": attr.label_list(
doc = "Patch files to apply onto the downloaded pnpm package. " +
"Paths in the patches are relative to the npm tarball root " +
"(which begins with `package/`).",
default = [],
),
"patch_args": attr.string_list(
doc = "Arguments for the patch tool. Defaults to [\"-p1\"].",
default = ["-p1"],
),
},
),
},
Expand Down
12 changes: 12 additions & 0 deletions npm/private/pnpm_extension.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def resolve_pnpm_repositories(mctx):
# Collect all the module tags and associated versions/integrities/options
integrity = {}
registrations = {}

# Patches and patch_args are per-repo-name (NOT per-version) and only
# accepted from the root module. Multiple tags for the same repo name
# concatenate their patches and the last patch_args wins.
patches_by_repo = {}
patch_args_by_repo = {}
for mod in mctx.modules:
for attr in mod.tags.pnpm:
if attr.name != DEFAULT_PNPM_REPO_NAME and not mod.is_root:
Expand All @@ -37,6 +43,10 @@ def resolve_pnpm_repositories(mctx):
""")
if not registrations.get(attr.name, False):
registrations[attr.name] = {}
if mod.is_root and (getattr(attr, "patches", None) or getattr(attr, "patch_args", None)):
patches_by_repo.setdefault(attr.name, [])
patches_by_repo[attr.name].extend(getattr(attr, "patches", []) or [])
patch_args_by_repo[attr.name] = list(attr.patch_args)

if attr.pnpm_version_from and attr.pnpm_version and attr.pnpm_version != DEFAULT_PNPM_VERSION:
fail("Cannot specify both pnpm_version = {} and pnpm_version_from = {}".format(attr.pnpm_version, attr.pnpm_version_from))
Expand Down Expand Up @@ -108,6 +118,8 @@ def resolve_pnpm_repositories(mctx):
"version": selected,
"include_npm": 0 < len([i for i in versions_map[selected] if i]),
"integrity": integrity.get(selected, None),
"patches": patches_by_repo.get(name, []),
"patch_args": patch_args_by_repo.get(name, ["-p1"]),
}

repositories[name] = selected
Expand Down
8 changes: 7 additions & 1 deletion npm/private/pnpm_repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ LATEST_PNPM_VERSION = PNPM_VERSIONS.keys()[-1]
# Default to the latest pnpm v10
DEFAULT_PNPM_VERSION = [v for v in PNPM_VERSIONS.keys() if v.startswith("10")][-1]

def pnpm_repository(name, pnpm_version, include_npm, integrity):
def pnpm_repository(name, pnpm_version, include_npm, integrity, patches = [], patch_args = ["-p1"]):
"""Import https://npmjs.com/package/pnpm and provide a js_binary to run the tool.

Useful as a way to run exactly the same pnpm as Bazel does, for example with:
Expand All @@ -24,6 +24,10 @@ def pnpm_repository(name, pnpm_version, include_npm, integrity):
`curl --silent https://registry.npmjs.org/pnpm | jq '.versions["8.6.11"].dist.integrity'`
integrity: integrity hash for the pnpm version (optional)
include_npm: if True, include the npm package along with pnpm binary
patches: list of Label targets pointing to .patch files to apply to the
extracted pnpm package (paths relative to the tarball root, which
starts with "package/"). Forwarded to the underlying npm_import.
patch_args: list of arguments for the patch tool. Defaults to ["-p1"].
"""

if native.existing_rule(name):
Expand All @@ -42,6 +46,8 @@ def pnpm_repository(name, pnpm_version, include_npm, integrity):
package = "pnpm",
root_package = "",
version = pnpm_version,
patches = patches,
patch_args = patch_args,
extra_build_content = "\n".join([
"""load("@aspect_rules_js//js:defs.bzl", "js_binary")""",
"""js_binary(
Expand Down
39 changes: 28 additions & 11 deletions npm/private/test/pnpm_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ load("//npm/private:pnpm_extension.bzl", "DEFAULT_PNPM_REPO_NAME", "resolve_pnpm
load("//npm/private:pnpm_repository.bzl", "DEFAULT_PNPM_VERSION", "LATEST_PNPM_VERSION")
load("//npm/private:versions.bzl", "PNPM_VERSIONS")

def _fake_pnpm_tag(version = None, name = DEFAULT_PNPM_REPO_NAME, integrity = None, pnpm_version_from = None, include_npm = False):
def _fake_pnpm_tag(version = None, name = DEFAULT_PNPM_REPO_NAME, integrity = None, pnpm_version_from = None, include_npm = False, patches = [], patch_args = ["-p1"]):
return struct(
name = name,
pnpm_version = version,
pnpm_version_from = pnpm_version_from,
pnpm_version_integrity = integrity,
include_npm = include_npm,
patches = patches,
patch_args = patch_args,
)

def _fake_mod(is_root, *pnpm_tags):
Expand Down Expand Up @@ -41,7 +43,7 @@ def _basic(ctx):
# - rules_js sets a default.
return _resolve_test(
ctx,
repositories = {"pnpm": {"version": "8.6.7", "integrity": "8.6.7-integrity", "include_npm": False}},
repositories = {"pnpm": {"version": "8.6.7", "integrity": "8.6.7-integrity", "include_npm": False, "patches": [], "patch_args": ["-p1"]}},
modules = [
_fake_mod(True),
_fake_mod(
Expand All @@ -56,7 +58,7 @@ def _from_package_json_simple(ctx):
# packageManager: "pnpm@1.2.3" -> version only, no integrity tuple
return _resolve_test(
ctx,
repositories = {"pnpm": {"version": "1.2.3", "integrity": None, "include_npm": False}},
repositories = {"pnpm": {"version": "1.2.3", "integrity": None, "include_npm": False, "patches": [], "patch_args": ["-p1"]}},
modules = [
_fake_mod(True, _fake_pnpm_tag(pnpm_version_from = "//:package.json")),
],
Expand All @@ -69,7 +71,7 @@ def _from_package_json_with_hash(ctx):
# packageManager: "pnpm@1.2.3+sha512.<base64>" -> (version, integrity) tuple
return _resolve_test(
ctx,
repositories = {"pnpm": {"version": "1.2.3", "integrity": "sha512-l0Ypl1YTeLb1KsXGFPOjuSOmUq1ayYcQAobkqi2EpqBkLp5F89AdMMRrErIL6w+GrreQv5qCvFnbQrZ/5p0aJQ==", "include_npm": False}},
repositories = {"pnpm": {"version": "1.2.3", "integrity": "sha512-l0Ypl1YTeLb1KsXGFPOjuSOmUq1ayYcQAobkqi2EpqBkLp5F89AdMMRrErIL6w+GrreQv5qCvFnbQrZ/5p0aJQ==", "include_npm": False, "patches": [], "patch_args": ["-p1"]}},
modules = [
_fake_mod(True, _fake_pnpm_tag(pnpm_version_from = "//:package.json")),
],
Expand All @@ -80,7 +82,7 @@ def _override(ctx):
# What happens when the root overrides the pnpm version.
return _resolve_test(
ctx,
repositories = {"pnpm": {"version": "9.1.0", "integrity": "sha512-Z/WHmRapKT5c8FnCOFPVcb6vT3U8cH9AyyK+1fsVeMaq07bEEHzLO6CzW+AD62IaFkcayDbIe+tT+dVLtGEnJA==", "include_npm": False}},
repositories = {"pnpm": {"version": "9.1.0", "integrity": "sha512-Z/WHmRapKT5c8FnCOFPVcb6vT3U8cH9AyyK+1fsVeMaq07bEEHzLO6CzW+AD62IaFkcayDbIe+tT+dVLtGEnJA==", "include_npm": False, "patches": [], "patch_args": ["-p1"]}},
notes = [],
modules = [
_fake_mod(
Expand All @@ -107,7 +109,7 @@ def _latest(ctx):
# - Accept a brittle test.
return _resolve_test(
ctx,
repositories = {"pnpm": {"version": LATEST_PNPM_VERSION, "integrity": PNPM_VERSIONS[LATEST_PNPM_VERSION], "include_npm": False}},
repositories = {"pnpm": {"version": LATEST_PNPM_VERSION, "integrity": PNPM_VERSIONS[LATEST_PNPM_VERSION], "include_npm": False, "patches": [], "patch_args": ["-p1"]}},
modules = [
_fake_mod(True, _fake_pnpm_tag(version = "latest")),
],
Expand All @@ -117,8 +119,8 @@ def _include_npm(ctx):
return _resolve_test(
ctx,
repositories = {
"pnpm": {"version": "9.1.0", "integrity": "sha512-Z/WHmRapKT5c8FnCOFPVcb6vT3U8cH9AyyK+1fsVeMaq07bEEHzLO6CzW+AD62IaFkcayDbIe+tT+dVLtGEnJA==", "include_npm": True},
"wnpm": {"version": "9.2.0", "integrity": "sha512-mKgP0RwucJZ0d2IwQQZDKz3cZ9z1S1qMAck/aKLNXgXmghhJUioG+3YoTUGiZg1eM08u47vykYO/LnObHa+ncQ==", "include_npm": True},
"pnpm": {"version": "9.1.0", "integrity": "sha512-Z/WHmRapKT5c8FnCOFPVcb6vT3U8cH9AyyK+1fsVeMaq07bEEHzLO6CzW+AD62IaFkcayDbIe+tT+dVLtGEnJA==", "include_npm": True, "patches": [], "patch_args": ["-p1"]},
"wnpm": {"version": "9.2.0", "integrity": "sha512-mKgP0RwucJZ0d2IwQQZDKz3cZ9z1S1qMAck/aKLNXgXmghhJUioG+3YoTUGiZg1eM08u47vykYO/LnObHa+ncQ==", "include_npm": True, "patches": [], "patch_args": ["-p1"]},
},
modules = [
_fake_mod(True, _fake_pnpm_tag(version = "9.1.0", include_npm = True)),
Expand All @@ -131,8 +133,8 @@ def _custom_name(ctx):
return _resolve_test(
ctx,
repositories = {
"my-pnpm": {"version": "9.1.0", "integrity": "sha512-Z/WHmRapKT5c8FnCOFPVcb6vT3U8cH9AyyK+1fsVeMaq07bEEHzLO6CzW+AD62IaFkcayDbIe+tT+dVLtGEnJA==", "include_npm": False},
"pnpm": {"version": "8.6.7", "integrity": "8.6.7-integrity", "include_npm": False},
"my-pnpm": {"version": "9.1.0", "integrity": "sha512-Z/WHmRapKT5c8FnCOFPVcb6vT3U8cH9AyyK+1fsVeMaq07bEEHzLO6CzW+AD62IaFkcayDbIe+tT+dVLtGEnJA==", "include_npm": False, "patches": [], "patch_args": ["-p1"]},
"pnpm": {"version": "8.6.7", "integrity": "8.6.7-integrity", "include_npm": False, "patches": [], "patch_args": ["-p1"]},
},
modules = [
_fake_mod(
Expand All @@ -152,7 +154,7 @@ def _integrity_conflict(ctx):
return _resolve_test(
ctx,
repositories = {
"pnpm": {"version": "8.6.7", "integrity": "dep-integrity", "include_npm": False},
"pnpm": {"version": "8.6.7", "integrity": "dep-integrity", "include_npm": False, "patches": [], "patch_args": ["-p1"]},
},
# Modules are *BFS* from root:
# https://bazel.build/rules/lib/builtins/module_ctx#modules
Expand All @@ -168,6 +170,19 @@ def _integrity_conflict(ctx):
],
)

def _patch_args_empty(ctx):
# An explicit patch_args = [] must not be silently dropped in favour of ["-p1"].
return _resolve_test(
ctx,
repositories = {"pnpm": {"version": "9.1.0", "integrity": "sha512-Z/WHmRapKT5c8FnCOFPVcb6vT3U8cH9AyyK+1fsVeMaq07bEEHzLO6CzW+AD62IaFkcayDbIe+tT+dVLtGEnJA==", "include_npm": False, "patches": ["//some:patch.patch"], "patch_args": []}},
modules = [
_fake_mod(
True,
_fake_pnpm_tag(version = "9.1.0", patches = ["//some:patch.patch"], patch_args = []),
),
],
)

def _default_version(ctx):
# Lockfile format is tied to the pnpm major. Pinning the default to v10
# keeps the lockfile format at v9; bumping the major changes the format
Expand Down Expand Up @@ -219,6 +234,7 @@ include_npm_test = unittest.make(_include_npm)
integrity_conflict_test = unittest.make(_integrity_conflict)
from_package_json_simple_test = unittest.make(_from_package_json_simple)
from_package_json_with_hash_test = unittest.make(_from_package_json_with_hash)
patch_args_empty_test = unittest.make(_patch_args_empty)
default_version_test = unittest.make(_default_version)
cpu_constraints_test = unittest.make(_cpu_constraints)
os_constraints_test = unittest.make(_os_constraints)
Expand All @@ -235,6 +251,7 @@ def pnpm_tests(name):
integrity_conflict_test,
from_package_json_simple_test,
from_package_json_with_hash_test,
patch_args_empty_test,
default_version_test,
cpu_constraints_test,
os_constraints_test,
Expand Down
Loading