Files
angular__angular/adev/tools/local_deps/index.bzl
Paul Gschwendtner f82898436e build(docs-infra): fix version mismatch of local built packages (#55282)
There is quite some trickery going on with the adev build related to
local packages:

- Adev builds using npm packages from `/node_modules`
- At runtime, we are adding `HEAD` packages for e.g. `@angular/core` to
  the bundles.
- At build time, the CLI, or Angular devkit may accidentally resolve to
  `@angular/core` from `/node_modules/`— which is the core version from
  npm, transitively installed via `@angular/docs`.

This causes a version mismatch, leading to issues like:

- CLI throwing because of a mismatch. https://github.com/angular/angular/issues/54858#issuecomment-2047188739
- Compiler changes not being picked up. https://github.com/angular/angular/issues/54858#issuecomment-2041322427

This commit attempts to fix this by:

- Linking all Angular `HEAD` packages into `adev/node_modules`. The
  current logic attempts to link into `/node_modules`, but this does not
  override existing `@angular/core`!
- Linking all direct external NPM packages, like
  `@angular_devkit/build-angular` into `adev/node_modules` without their
  transitive deps. This allows proper resolution of e.g. compiler as
  node looks in `adev/node_modules` first, and falls back for the rest
  to the execroot `node_modules`, or symlink target destination (if
  `preserveSymlinks=false`).

Note: This is still not 100% ideal because a direct external NPM
dependency may have a transitive dependency that has another transitive
dependency on `@angular/core`. In those cases, the may be a conflict
that is not resolvable until we switch to a Bazel toolchain with better
first party resolution support.

PR Close #55282
2024-04-10 10:48:38 -07:00

85 lines
3.3 KiB
Python

load("//:packages.bzl", "ALL_PACKAGES", "to_package_label")
load("@build_bazel_rules_nodejs//internal/linker:npm_link.bzl", "npm_link")
load("//adev/tools/local_deps:filter_external_npm_deps.bzl", "filter_external_npm_deps")
def ensure_local_package_deps(deps):
"""Replaces dependencies with their local-linked variants."""
return [":%s" % _filtered_transitives_name(dep) for dep in deps]
def link_local_packages(all_deps):
"""Create targets needed for building adev against local angular packages.
Creates targets that link Angular packages, as well as targets to be used
in place of any deps required to build and test adev. These targets filter
out any transitive deps on the npm packages and must be used in place of
any original list of deps.
Use the helper `ensure_local_package_deps()` to translate a list of deps
to the equivalent "filtered" target that this rule creates.
Args:
all_deps: label list of all deps required to build and test adev
"""
local_angular_deps = [dep for dep in all_deps if _is_angular_dep(dep)]
local_angular_package_names = [_angular_dep_to_pkg_name(dep) for dep in local_angular_deps]
# Link local angular packages in place of their npm equivalent
for dep in local_angular_deps:
pkg_name = _angular_dep_to_pkg_name(dep)
npm_link(
name = _npm_link_name(pkg_name),
target = to_package_label(pkg_name),
package_name = pkg_name,
package_path = native.package_name(),
tags = ["manual"],
)
# Special case deps that must be testonly
testonly_deps = [
"@npm//@angular/build-tooling/bazel/browsers/chromium",
]
# Stamp a corresponding target for each dep that filters out transitive
# dependencies on external npm packages. This help the rules_nodejs linker,
# which fails to link local packages into transitive dependencies of npm deps.
for dep in all_deps:
target = dep
if dep in local_angular_deps:
pkg_name = _angular_dep_to_pkg_name(dep)
# We don't need to filter transitives on local packages as they
# depend on each other locally.
native.alias(
name = _filtered_transitives_name(dep),
actual = ":%s" % _npm_link_name(pkg_name),
tags = ["manual"],
)
else:
filter_external_npm_deps(
name = _filtered_transitives_name(dep),
target = target,
testonly = True if dep in testonly_deps else False,
angular_packages = local_angular_package_names,
tags = ["manual"],
)
def _is_angular_dep(dep):
"""Check if a dep , e.g., @npm//@angular/core corresonds to a local Angular pacakge."""
return dep.startswith("@npm//") and (_angular_dep_to_pkg_name(dep) in ALL_PACKAGES)
def _angular_dep_to_pkg_name(dep):
"""E.g., @npm//@angular/core => '@angular/core'"""
label = Label(dep)
return label.package
def _npm_link_name(pkg_name):
return "local_head_%s" % pkg_name.replace("@", "_").replace("/", "_")
def _filtered_transitives_name(dep):
if dep.startswith(":"):
return "%s_without_transitive_deps" % dep[1:]
else:
label = Label(dep)
return "%s_without_transitive_deps" % label.package.replace("@", "_").replace("/", "_")