From 63e4bba0ca3f5572528cc151427ce21a4dc38fad Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 30 Jan 2025 14:55:47 +0000 Subject: [PATCH 1/4] build: allow for `@devinfra` repository to be used directly This allows e.g. the CLI repository to directly fetch the dev-infra repository and leverage it for e.g. the RBE toolchains, browser toolchain, or git toolchain. More to follow. --- WORKSPACE | 2 +- bazel/browsers/browser_archive_repo.bzl | 2 +- package.bzl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index d3b94767d..b39a3eeb9 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,5 +1,5 @@ workspace( - name = "dev-infra", + name = "devinfra", managed_directories = {"@npm": ["node_modules"]}, ) diff --git a/bazel/browsers/browser_archive_repo.bzl b/bazel/browsers/browser_archive_repo.bzl index edcfeda19..0bccf21c3 100644 --- a/bazel/browsers/browser_archive_repo.bzl +++ b/bazel/browsers/browser_archive_repo.bzl @@ -27,7 +27,7 @@ def _browser_archive_impl(ctx): # `BUILD.bazel` file that exposes the archive files, together with the specified # named files using the `browser_configure` rule. ctx.file("BUILD.bazel", content = """ -load("@dev-infra//bazel/browsers:browser_configure.bzl", "browser_configure") +load("@devinfra//bazel/browsers:browser_configure.bzl", "browser_configure") licenses(%s) diff --git a/package.bzl b/package.bzl index 2d5c54bca..cebcd20b8 100644 --- a/package.bzl +++ b/package.bzl @@ -9,7 +9,7 @@ noStampSubstitutions = dict(stampSubstitutions, **{}) basePackageSubstitutions = { "(#|//)\\s+BEGIN-DEV-ONLY[\\w\\W]+?(#|//)\\s+END-DEV-ONLY": "", " \"prepare\": \"husky\",\n": "", - "@dev-infra//bazel/": "@npm//@angular/build-tooling/bazel/", + "@devinfra//bazel/": "@npm//@angular/build-tooling/bazel/", "rlocation \"dev-infra/": "rlocation \"npm/@angular/build-tooling/", "//bazel/": "@npm//@angular/build-tooling/bazel/", "//bazel:": "@npm//@angular/build-tooling/bazel:", From d2c7dfb1c0ca1a092c83c3f59a88fa53fd9083f7 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 6 Feb 2025 21:21:59 +0000 Subject: [PATCH 2/4] build: setup `rules_js` properly and expose initialization helpers This allows for the dev-infra repository to be conveniently used in e.g. the angular-cli repository, allowing for rules to be consumed like we are testing and building them inside this workspace. This greatly simplfies developer productivity for dev-infra and also allows us to avoid complexity around shipping, or bundling. Consider examples like API extractor which cannot be bundled due to reliance on assets inside the `node_modules` directory. --- .bazelrc | 6 +- .bazelversion | 2 +- BUILD.bazel | 12 +- WORKSPACE | 48 ++- bazel/BUILD.bazel | 12 + bazel/browsers/test/BUILD.bazel | 2 +- bazel/http-server/launcher_template.sh | 2 +- bazel/package.json | 11 + bazel/pnpm-lock.yaml | 411 +++++++++++++++++++++++++ bazel/remote-execution/BUILD.bazel | 1 - bazel/remote-execution/cpp/BUILD.bazel | 58 ---- bazel/setup_dependencies_1.bzl | 11 + bazel/setup_dependencies_2.bzl | 4 + ng-dev/utils/testing/bazel-env.ts | 2 +- package.bzl | 3 +- tools/karma/karma.bzl | 2 +- tsconfig.json | 1 - 17 files changed, 513 insertions(+), 75 deletions(-) create mode 100644 bazel/package.json create mode 100644 bazel/pnpm-lock.yaml delete mode 100644 bazel/remote-execution/cpp/BUILD.bazel create mode 100644 bazel/setup_dependencies_1.bzl create mode 100644 bazel/setup_dependencies_2.bzl diff --git a/.bazelrc b/.bazelrc index cc960066c..ec6ea5ebe 100644 --- a/.bazelrc +++ b/.bazelrc @@ -6,6 +6,10 @@ test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test build --sandbox_default_allow_network=false test --sandbox_default_allow_network=false +# Required by `rules_ts`. +common --@aspect_rules_ts//ts:skipLibCheck=always +common --@aspect_rules_ts//ts:default_to_tsc_transpiler + # Turn off legacy external runfiles build --nolegacy_external_runfiles run --nolegacy_external_runfiles @@ -74,8 +78,6 @@ build:remote --google_default_credentials # Setup the toolchain and platform for the remote build execution. The platform # is provided by the shared dev-infra package and targets k8 remote containers. -build:remote --crosstool_top=//bazel/remote-execution/cpp:cc_toolchain_suite -build:remote --extra_toolchains=//bazel/remote-execution/cpp:cc_toolchain build:remote --extra_execution_platforms=//bazel/remote-execution:platform_with_network build:remote --host_platform=//bazel/remote-execution:platform_with_network build:remote --platforms=//bazel/remote-execution:platform_with_network diff --git a/.bazelversion b/.bazelversion index 1e20ec35c..f22d756da 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -5.4.0 \ No newline at end of file +6.5.0 diff --git a/BUILD.bazel b/BUILD.bazel index 490a0c992..232d72a6d 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,8 +1,16 @@ +load("@aspect_rules_ts//ts:defs.bzl", rules_js_tsconfig = "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") +load("@npm//@bazel/typescript:index.bzl", "ts_config") + # BEGIN-INTERNAL load("//:package.bzl", "NPM_PACKAGE_SUBSTITUTIONS") load("//tools:defaults.bzl", "pkg_npm") -load("@npm//@bazel/typescript:index.bzl", "ts_config") -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") + +rules_js_tsconfig( + name = "rjs-tsconfig", + src = "tsconfig.json", + visibility = ["//bazel:__subpackages__"], +) exports_files([ "package.json", diff --git a/WORKSPACE b/WORKSPACE index b39a3eeb9..7e20e629b 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,10 +1,11 @@ workspace( name = "devinfra", - managed_directories = {"@npm": ["node_modules"]}, ) load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file") +NODE_VERSION = "18.20.0" + # The PKG rules are needed to build tar packages for integration tests. The builtin # rule in `@bazel_tools` is not Windows compatible and outdated. http_archive( @@ -50,9 +51,24 @@ nodejs_register_toolchains( "18.20.0-windows_amd64": ("node-v18.20.0-win-x64.zip", "node-v18.20.0-win-x64", "1c0aab05cc6836a8f5148cca345b92ebc948a4a2013f18d117b7ade6ff05aca6"), }, # We need at least Node 18.17 due to some transitive dependencies. - node_version = "18.20.0", + node_version = NODE_VERSION, ) +http_archive( + name = "aspect_rules_js", + sha256 = "875b8d01af629dbf626eddc5cf239c9f0da20330f4d99ad956afc961096448dd", + strip_prefix = "rules_js-2.1.3", + url = "https://github.com/aspect-build/rules_js/releases/download/v2.1.3/rules_js-v2.1.3.tar.gz", +) + +load("@aspect_rules_js//js:repositories.bzl", "rules_js_dependencies") + +rules_js_dependencies() + +load("@aspect_rules_js//js:toolchains.bzl", "rules_js_register_toolchains") + +rules_js_register_toolchains() + load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install") yarn_install( @@ -60,6 +76,9 @@ yarn_install( # Yarn Berry/v2+ expects `--immutable` instead of `--frozen-lockfile`. args = ["--immutable"], data = [ + "//:.yarn/patches/@angular-bazel-npm.patch", + "//:.yarn/patches/@bazel-jasmine-npm.patch", + "//:.yarn/patches/@octokit-graphql-schema-npm-15.3.0-4046a59648.patch", "//:.yarn/releases/yarn-4.6.0.cjs", "//:.yarnrc.yml", ], @@ -68,9 +87,6 @@ yarn_install( # 2. Incompatibilites with the `ts_library` rule. exports_directories_only = False, package_json = "//:package.json", - # We prefer to symlink the `node_modules` to only maintain a single install. - # See https://github.com/angular/dev-infra/pull/446#issuecomment-1059820287 for details. - symlink_node_modules = True, yarn = "//:.yarn/releases/yarn-4.6.0.cjs", yarn_lock = "//:yarn.lock", ) @@ -128,3 +144,25 @@ http_file( sha256 = "61ce1dc62fdcfd6d68624a403e0f04c5fd5136d933b681467aad1ad2d00dbb03", urls = ["https://raw.githubusercontent.com/bazelbuild/bazel/5.0.0/src/main/protobuf/test_status.proto"], ) + +http_archive( + name = "aspect_rules_ts", + sha256 = "013a10b2b457add73b081780e604778eb50a141709f9194298f97761acdcc169", + strip_prefix = "rules_ts-3.4.0", + url = "https://github.com/aspect-build/rules_ts/releases/download/v3.4.0/rules_ts-v3.4.0.tar.gz", +) + +load("@aspect_rules_ts//ts:repositories.bzl", "rules_ts_dependencies") + +rules_ts_dependencies( + ts_integrity = "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + ts_version_from = "//bazel:package.json", +) + +load("//bazel:setup_dependencies_1.bzl", "setup_dependencies_1") + +setup_dependencies_1() + +load("//bazel:setup_dependencies_2.bzl", "setup_dependencies_2") + +setup_dependencies_2() diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel index e0fb2bcf5..927517910 100644 --- a/bazel/BUILD.bazel +++ b/bazel/BUILD.bazel @@ -1,3 +1,15 @@ +load("@aspect_rules_ts//ts:defs.bzl", "ts_config") +load("@devinfra_npm//:defs.bzl", "npm_link_all_packages") + +npm_link_all_packages() + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + visibility = ["//bazel:__subpackages__"], + deps = ["//:rjs-tsconfig"], +) + filegroup( name = "static_files", srcs = [ diff --git a/bazel/browsers/test/BUILD.bazel b/bazel/browsers/test/BUILD.bazel index 5e9e1f22a..02570669b 100644 --- a/bazel/browsers/test/BUILD.bazel +++ b/bazel/browsers/test/BUILD.bazel @@ -27,6 +27,6 @@ js_library( spec_bundle( name = "test_bundle", platform = "browser", - workspace_name = "dev-infra", + workspace_name = "devinfra", deps = [":test_lib"], ) diff --git a/bazel/http-server/launcher_template.sh b/bazel/http-server/launcher_template.sh index 19c712ae4..9cac4e748 100644 --- a/bazel/http-server/launcher_template.sh +++ b/bazel/http-server/launcher_template.sh @@ -26,7 +26,7 @@ fi # resolve the "nodejs_binary" executable with different file extensions on # windows, but since we already run this launcher as part of a "sh_binary", we # can safely execute another shell script from the current shell. -serverBin=$(rlocation "dev-infra/bazel/http-server/server_bin.sh") +serverBin=$(rlocation "devinfra/bazel/http-server/server_bin.sh") # Start the server with the given arguments. The arguments will be # substituted based on the rule attributes. diff --git a/bazel/package.json b/bazel/package.json new file mode 100644 index 000000000..9cd461d43 --- /dev/null +++ b/bazel/package.json @@ -0,0 +1,11 @@ +{ + "name": "@devinfra/bazel", + "dependencies": { + "@microsoft/api-extractor": "7.49.2", + "typescript": "5.7.3", + "@types/node": "22.13.0" + }, + "pnpm": { + "onlyBuiltDependencies": [] + } +} diff --git a/bazel/pnpm-lock.yaml b/bazel/pnpm-lock.yaml new file mode 100644 index 000000000..aca69a081 --- /dev/null +++ b/bazel/pnpm-lock.yaml @@ -0,0 +1,411 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@microsoft/api-extractor': + specifier: 7.49.2 + version: 7.49.2(@types/node@22.13.0) + '@types/node': + specifier: 22.13.0 + version: 22.13.0 + typescript: + specifier: 5.7.3 + version: 5.7.3 + +packages: + + '@microsoft/api-extractor-model@7.30.3': + resolution: {integrity: sha512-yEAvq0F78MmStXdqz9TTT4PZ05Xu5R8nqgwI5xmUmQjWBQ9E6R2n8HB/iZMRciG4rf9iwI2mtuQwIzDXBvHn1w==} + + '@microsoft/api-extractor@7.49.2': + resolution: {integrity: sha512-DI/WnvhbkHcucxxc4ys00ejCiViFls5EKPrEfe4NV3GGpVkoM5ZXF61HZNSGA8IG0oEV4KfTqIa59Rc3wdMopw==} + hasBin: true + + '@microsoft/tsdoc-config@0.17.1': + resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} + + '@microsoft/tsdoc@0.15.1': + resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + + '@rushstack/node-core-library@5.11.0': + resolution: {integrity: sha512-I8+VzG9A0F3nH2rLpPd7hF8F7l5Xb7D+ldrWVZYegXM6CsKkvWc670RlgK3WX8/AseZfXA/vVrh0bpXe2Y2UDQ==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/rig-package@0.5.3': + resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==} + + '@rushstack/terminal@0.14.6': + resolution: {integrity: sha512-4nMUy4h0u5PGXVG71kEA9uYI3l8GjVqewoHOFONiM6fuqS51ORdaJZ5ZXB2VZEGUyfg1TOTSy88MF2cdAy+lqA==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@4.23.4': + resolution: {integrity: sha512-pqmzDJCm0TS8VyeqnzcJ7ncwXgiLDQ6LVmXXfqv2nPL6VIz+UpyTpNVfZRJpyyJ+UDxqob1vIj2liaUfBjv8/A==} + + '@types/argparse@1.0.38': + resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} + + '@types/node@22.13.0': + resolution: {integrity: sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==} + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.13.0: + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + minimatch@3.0.8: + resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + +snapshots: + + '@microsoft/api-extractor-model@7.30.3(@types/node@22.13.0)': + dependencies: + '@microsoft/tsdoc': 0.15.1 + '@microsoft/tsdoc-config': 0.17.1 + '@rushstack/node-core-library': 5.11.0(@types/node@22.13.0) + transitivePeerDependencies: + - '@types/node' + + '@microsoft/api-extractor@7.49.2(@types/node@22.13.0)': + dependencies: + '@microsoft/api-extractor-model': 7.30.3(@types/node@22.13.0) + '@microsoft/tsdoc': 0.15.1 + '@microsoft/tsdoc-config': 0.17.1 + '@rushstack/node-core-library': 5.11.0(@types/node@22.13.0) + '@rushstack/rig-package': 0.5.3 + '@rushstack/terminal': 0.14.6(@types/node@22.13.0) + '@rushstack/ts-command-line': 4.23.4(@types/node@22.13.0) + lodash: 4.17.21 + minimatch: 3.0.8 + resolve: 1.22.10 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.7.2 + transitivePeerDependencies: + - '@types/node' + + '@microsoft/tsdoc-config@0.17.1': + dependencies: + '@microsoft/tsdoc': 0.15.1 + ajv: 8.12.0 + jju: 1.4.0 + resolve: 1.22.10 + + '@microsoft/tsdoc@0.15.1': {} + + '@rushstack/node-core-library@5.11.0(@types/node@22.13.0)': + dependencies: + ajv: 8.13.0 + ajv-draft-04: 1.0.0(ajv@8.13.0) + ajv-formats: 3.0.1(ajv@8.13.0) + fs-extra: 11.3.0 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.10 + semver: 7.5.4 + optionalDependencies: + '@types/node': 22.13.0 + + '@rushstack/rig-package@0.5.3': + dependencies: + resolve: 1.22.10 + strip-json-comments: 3.1.1 + + '@rushstack/terminal@0.14.6(@types/node@22.13.0)': + dependencies: + '@rushstack/node-core-library': 5.11.0(@types/node@22.13.0) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 22.13.0 + + '@rushstack/ts-command-line@4.23.4(@types/node@22.13.0)': + dependencies: + '@rushstack/terminal': 0.14.6(@types/node@22.13.0) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + + '@types/argparse@1.0.38': {} + + '@types/node@22.13.0': + dependencies: + undici-types: 6.20.0 + + ajv-draft-04@1.0.0(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv-formats@3.0.1(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.13.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + balanced-match@1.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + concat-map@0.0.1: {} + + fast-deep-equal@3.1.3: {} + + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + function-bind@1.1.2: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + import-lazy@4.0.0: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + jju@1.4.0: {} + + json-schema-traverse@1.0.0: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + lodash@4.17.21: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + minimatch@3.0.8: + dependencies: + brace-expansion: 1.1.11 + + path-parse@1.0.7: {} + + punycode@2.3.1: {} + + require-from-string@2.0.2: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + string-argv@0.3.2: {} + + strip-json-comments@3.1.1: {} + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + typescript@5.7.2: {} + + typescript@5.7.3: {} + + undici-types@6.20.0: {} + + universalify@2.0.1: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + yallist@4.0.0: {} diff --git a/bazel/remote-execution/BUILD.bazel b/bazel/remote-execution/BUILD.bazel index e22fca517..55652e9dd 100644 --- a/bazel/remote-execution/BUILD.bazel +++ b/bazel/remote-execution/BUILD.bazel @@ -47,6 +47,5 @@ filegroup( srcs = [ "BUILD.bazel", "index.bzl", - "//bazel/remote-execution/cpp:files", ], ) diff --git a/bazel/remote-execution/cpp/BUILD.bazel b/bazel/remote-execution/cpp/BUILD.bazel deleted file mode 100644 index 43317c803..000000000 --- a/bazel/remote-execution/cpp/BUILD.bazel +++ /dev/null @@ -1,58 +0,0 @@ -load("@bazel_tools//tools/cpp:cc_toolchain_config.bzl", "cc_toolchain_config") - -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "files", - srcs = ["BUILD.bazel"], -) - -cc_toolchain_suite( - name = "cc_toolchain_suite", - tags = ["manual"], - toolchains = { - "k8": ":cc_compiler_k8", - }, -) - -toolchain( - name = "cc_toolchain", - exec_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - "@bazel_tools//tools/cpp:clang", - ], - target_compatible_with = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - ], - toolchain = ":cc_compiler_k8", - toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", -) - -# Basic CC toolchain for k8 remote containers. Based on the default k8 -# toolchain provided in Bazel (but unfortunately internal). -# https://github.com/bazelbuild/bazel/blob/c951753097b45cfb9be512c02199aa891b9646b8/tools/cpp/BUILD.tools#L298-L311 -cc_toolchain( - name = "cc_compiler_k8", - all_files = ":empty", - ar_files = ":empty", - as_files = ":empty", - compiler_files = ":empty", - dwp_files = ":empty", - linker_files = ":empty", - objcopy_files = ":empty", - strip_files = ":empty", - supports_param_files = 1, - toolchain_config = ":k8_toolchain_config", - toolchain_identifier = "cc-k8-compiler", -) - -cc_toolchain_config( - name = "k8_toolchain_config", - compiler = "compiler", - cpu = "local", -) - -# Empty filegroup used for defining the CC toolchain. -filegroup(name = "empty") diff --git a/bazel/setup_dependencies_1.bzl b/bazel/setup_dependencies_1.bzl new file mode 100644 index 000000000..82f57a5ac --- /dev/null +++ b/bazel/setup_dependencies_1.bzl @@ -0,0 +1,11 @@ +load("@aspect_rules_js//npm:repositories.bzl", "npm_translate_lock") + +def setup_dependencies_1(): + npm_translate_lock( + name = "devinfra_npm", + data = [ + "@devinfra//bazel:package.json", + "@devinfra//bazel:pnpm-workspace.yaml", + ], + pnpm_lock = "@devinfra//bazel:pnpm-lock.yaml", + ) diff --git a/bazel/setup_dependencies_2.bzl b/bazel/setup_dependencies_2.bzl new file mode 100644 index 000000000..2fcfbb49e --- /dev/null +++ b/bazel/setup_dependencies_2.bzl @@ -0,0 +1,4 @@ +load("@devinfra_npm//:repositories.bzl", "npm_repositories") + +def setup_dependencies_2(): + npm_repositories() diff --git a/ng-dev/utils/testing/bazel-env.ts b/ng-dev/utils/testing/bazel-env.ts index 7e552b6ab..ac6a75f48 100644 --- a/ng-dev/utils/testing/bazel-env.ts +++ b/ng-dev/utils/testing/bazel-env.ts @@ -14,7 +14,7 @@ import {join} from 'path'; * variable is automatically set by Bazel for tests. Bazel expects tests "not attempt to remove, * chmod, or otherwise alter [TEST_TMPDIR]," so a subdirectory path is used to be created/destroyed. */ -export const testTmpDir: string = join(process.env['TEST_TMPDIR']!, 'dev-infra'); +export const testTmpDir: string = join(process.env['TEST_TMPDIR']!, 'devinfra'); /** * Prepares the temporary test directory, deleting previous contents if present. diff --git a/package.bzl b/package.bzl index cebcd20b8..d87b12f43 100644 --- a/package.bzl +++ b/package.bzl @@ -10,7 +10,7 @@ basePackageSubstitutions = { "(#|//)\\s+BEGIN-DEV-ONLY[\\w\\W]+?(#|//)\\s+END-DEV-ONLY": "", " \"prepare\": \"husky\",\n": "", "@devinfra//bazel/": "@npm//@angular/build-tooling/bazel/", - "rlocation \"dev-infra/": "rlocation \"npm/@angular/build-tooling/", + "rlocation \"devinfra/": "rlocation \"npm/@angular/build-tooling/", "//bazel/": "@npm//@angular/build-tooling/bazel/", "//bazel:": "@npm//@angular/build-tooling/bazel:", "//lint-rules/tslint/": "@npm//@angular/build-tooling/tslint/", @@ -35,6 +35,7 @@ BZL_DEFAULTS_ALLOW_PACKAGES = [ "", "apps", "bazel/browsers/update-script", + "bazel/api-golden", "github-actions", "ng-dev", "tools", diff --git a/tools/karma/karma.bzl b/tools/karma/karma.bzl index 53a2ba4a8..7d77ab90d 100644 --- a/tools/karma/karma.bzl +++ b/tools/karma/karma.bzl @@ -5,7 +5,7 @@ def karma_web_test(name, specs = [], external = [], **kwargs): spec_bundle( name = "%s_test_bundle" % name, platform = "browser", - workspace_name = "dev-infra", + workspace_name = "devinfra", bootstrap = ["//tools/karma:bootstrap"], deps = specs, external = external, diff --git a/tsconfig.json b/tsconfig.json index 342ed0e24..52f03f63d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,6 @@ "lib": ["es2021", "dom"], "experimentalDecorators": true, "strict": true, - "outDir": "./dist/tsc-non-bazel-out", "skipLibCheck": true, "noImplicitOverride": true, // TODO(ESM): Remove this when Bazel also uses the Node resolution. From afa41422a5e2b04246a4a3fc44f4117b3e783277 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 6 Feb 2025 21:24:31 +0000 Subject: [PATCH 3/4] build: simplify api-golden rule and make it `rules_js` compatible Simplifies the api-golden rule by removing the single file mode, and using synthethic npm packages here. In addition, this commit makes the rule compatible with `rules_js`, by providing another `_rjs.bzl` file, that will eventually take over the legacy exports that currently call into the `rjs` variant! (so in practice this rule is alrady using rjs and can work with `rules_nodejs` as an interop layer) --- .prettierignore | 2 + bazel/api-golden/BUILD.bazel | 16 +-- bazel/api-golden/find_entry_points.ts | 2 +- bazel/api-golden/index.bzl | 124 ++++++------------ bazel/api-golden/index.ts | 67 ---------- bazel/api-golden/index_npm_packages.ts | 47 ++++--- bazel/api-golden/index_rjs.bzl | 55 ++++++++ bazel/api-golden/module_mappings.ts | 6 +- bazel/api-golden/test/BUILD.bazel | 8 +- .../index.api.md} | 2 +- bazel/api-golden/test_api_report.ts | 56 ++++---- 11 files changed, 166 insertions(+), 219 deletions(-) delete mode 100644 bazel/api-golden/index.ts create mode 100644 bazel/api-golden/index_rjs.bzl rename bazel/api-golden/test/goldens/{test_golden.api.md => explicit_file/index.api.md} (90%) diff --git a/.prettierignore b/.prettierignore index 2eb979ad4..55b01b4b8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -25,3 +25,5 @@ github-actions/unified-status-check/main.js .github/local-actions/changelog/main.js bazel/map-size-tracking/test/size-golden.json + +bazel/pnpm-lock.yaml diff --git a/bazel/api-golden/BUILD.bazel b/bazel/api-golden/BUILD.bazel index f7c38aca4..37a3df5f3 100644 --- a/bazel/api-golden/BUILD.bazel +++ b/bazel/api-golden/BUILD.bazel @@ -1,4 +1,4 @@ -load("//bazel:defaults.bzl", "ts_library") +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") package(default_visibility = ["//visibility:public"]) @@ -7,25 +7,21 @@ exports_files([ "index_npm_packages.ts", ]) -ts_library( +ts_project( name = "api-golden", srcs = [ "find_entry_points.ts", - "index.ts", "index_npm_packages.ts", "module_mappings.ts", "patch-host.ts", "path-normalize.ts", "test_api_report.ts", ], - # A tsconfig needs to be specified as otherwise `ts_library` will look for the config - # in `//:package.json` and this breaks when the BUILD file is copied to `@npm//`. - tsconfig = "//:tsconfig.json", + tsconfig = "//bazel:tsconfig", deps = [ - "@npm//@bazel/runfiles", - "@npm//@microsoft/api-extractor", - "@npm//@types/node", - "@npm//typescript", + "//bazel:node_modules/@microsoft/api-extractor", + "//bazel:node_modules/@types/node", + "//bazel:node_modules/typescript", ], ) diff --git a/bazel/api-golden/find_entry_points.ts b/bazel/api-golden/find_entry_points.ts index 5f10bca5b..f442005a9 100644 --- a/bazel/api-golden/find_entry_points.ts +++ b/bazel/api-golden/find_entry_points.ts @@ -9,7 +9,7 @@ import {dirname, join, relative} from 'path'; import {lstatSync, readFileSync, readdirSync} from 'fs'; -import {PackageJson} from './index_npm_packages'; +import {PackageJson} from './index_npm_packages.js'; /** Interface describing a resolved NPM package entry point. */ export interface PackageEntryPoint { diff --git a/bazel/api-golden/index.bzl b/bazel/api-golden/index.bzl index 308b0196a..a43e2fd32 100644 --- a/bazel/api-golden/index.bzl +++ b/bazel/api-golden/index.bzl @@ -1,5 +1,6 @@ -load("//bazel:extract_types.bzl", "extract_types") -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "nodejs_test") +load("@bazel_skylib//rules:write_file.bzl", "write_file") +load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") +load("//bazel/api-golden:index_rjs.bzl", _rjs_api_golden_test_npm_package = "api_golden_test_npm_package") nodejs_test_args = [ # Needed so that node doesn't walk back to the source directory. @@ -10,18 +11,8 @@ nodejs_test_args = [ default_strip_export_pattern = "^ɵ(?!ɵdefineInjectable|ɵinject|ɵInjectableDef)" -def _escape_regex_for_arg(value): - """Escapes a Regular expression so that it can be passed as process argument.""" - return "\"%s\"" % value - def extract_module_names_from_npm_targets(type_targets): - """Extracts the module names from a list of NPM targets. - - For example: Consider the `@npm//@types/node` target. This function extracts - `@types/node` from the label. This is needed so that the Node types can be - resolved from within the test runner through runfile resolution. - """ - module_names = [] + types = {} for type_target in type_targets: type_label = Label(type_target) @@ -31,9 +22,9 @@ def extract_module_names_from_npm_targets(type_targets): fail("Expected type targets to be part of the `@npm` workspace." + "e.g. `@npm//@types/nodes`.") - module_names.append(type_package) + types[type_target] = type_package - return module_names + return types def api_golden_test( name, @@ -43,52 +34,33 @@ def api_golden_test( strip_export_pattern = default_strip_export_pattern, types = [], **kwargs): - """Builds an API report for the specified entry-point and compares it against the - specified golden - - Args; - name: Name of the test target - golden: Manifest path to the golden file - entry_point: Manifest path to the type definition entry-point. - data: Runtime dependenices needed for the rule (e.g. transitive type definitions) - strip_export_pattern: An optional regular expression to filter out exports from the golden. - types: Optional list of type targets to make available in the API report generation. - """ - - quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern) - - kwargs["tags"] = kwargs.get("tags", []) + ["api_guard"] + write_file( + name = "%s_synthetic_package_json" % name, + out = "package.json", + content = [json.encode({ + "name": name, + "exports": { + ".": { + "types": entry_point, + }, + }, + })], + ) - # For API golden tests not running against a NPM package, we extract all transitive - # declarations of the specified `data` targets. This is necessary because API extractor - # needs to resolve other targets that have been linked by the Bazel NodeJS rules. The - # linker by default only provides access to JavaScript sources, but the API extractor is - # specifically concerned with type definitions that we can extract manually here. - extract_types( - name = "%s_data_typings" % name, + pkg_npm( + name = "%s_synthetic_package" % name, + deps = data + ["%s_synthetic_package_json" % name], testonly = True, - deps = data, ) - test_data = ["//bazel/api-golden", "//:package.json", ":%s_data_typings" % name] + \ - data + types - - nodejs_test( + _rjs_api_golden_test_npm_package( + no_copy_to_bin = types, name = name, - data = test_data, - entry_point = "//bazel/api-golden:index.ts", - templated_args = nodejs_test_args + [golden, entry_point, "false", quoted_export_pattern] + - extract_module_names_from_npm_targets(types), - **kwargs - ) - - nodejs_binary( - name = name + ".accept", - testonly = True, - data = test_data, - entry_point = "//bazel/api-golden:index.ts", - templated_args = nodejs_test_args + [golden, entry_point, "true", quoted_export_pattern] + - extract_module_names_from_npm_targets(types), + golden_dir = fixup_path_for_rules_js(golden), + data = [":%s_synthetic_package" % name] + data, + npm_package = "%s/%s_synthetic_package" % (native.package_name(), name), + strip_export_pattern = strip_export_pattern, + types = extract_module_names_from_npm_targets(types), **kwargs ) @@ -100,37 +72,17 @@ def api_golden_test_npm_package( strip_export_pattern = default_strip_export_pattern, types = [], **kwargs): - """Builds an API report for all entry-points within the given NPM package and compares it - against goldens within the specified directory. - - Args; - name: Name of the test target - golden_dir: Manifest path to the golden directory - npm_package: Manifest path to the NPM package. - data: Runtime dependenices needed for the rule (e.g. the tree artifact of the NPM package) - strip_export_pattern: An optional regular expression to filter out exports from the golden. - types: Optional list of type targets to make available in the API report generation. - """ - - quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern) - - kwargs["tags"] = kwargs.get("tags", []) + ["api_guard"] - - nodejs_test( + _rjs_api_golden_test_npm_package( name = name, - data = ["//bazel/api-golden"] + data + types, - entry_point = "//bazel/api-golden:index_npm_packages.ts", - templated_args = nodejs_test_args + [golden_dir, npm_package, "false", quoted_export_pattern] + - extract_module_names_from_npm_targets(types), + no_copy_to_bin = types, + golden_dir = fixup_path_for_rules_js(golden_dir), + npm_package = fixup_path_for_rules_js(npm_package), + data = data, + strip_export_pattern = strip_export_pattern, + types = extract_module_names_from_npm_targets(types), **kwargs ) - nodejs_binary( - name = name + ".accept", - testonly = True, - data = ["//bazel/api-golden"] + data + types, - entry_point = "//bazel/api-golden:index_npm_packages.ts", - templated_args = nodejs_test_args + [golden_dir, npm_package, "true", quoted_export_pattern] + - extract_module_names_from_npm_targets(types), - **kwargs - ) +def fixup_path_for_rules_js(p): + segs = p.split("/") + return "/".join(segs[1:]) diff --git a/bazel/api-golden/index.ts b/bazel/api-golden/index.ts deleted file mode 100644 index edf514798..000000000 --- a/bazel/api-golden/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright Google LLC - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {runfiles} from '@bazel/runfiles'; -import {testApiGolden} from './test_api_report'; - -/** - * Entry point for the `api_golden_test` Bazel rule. This function builds an API report for - * the specified entry point file and compares it against the specified golden file. - */ -async function main( - goldenFilePath: string, - entryPointFilePath: string, - approveGolden: boolean, - stripExportPattern: RegExp, - typePackageNames: string[], -) { - // TODO(ESM) This can be replaced with an actual ESM import when `ts_library` is - // guaranteed to be ESM-only and supports the `mts` extension. - const chalk = {red: (v: string) => v, yellow: (v: string) => v}; - - const {succeeded, apiReportChanged} = await testApiGolden( - goldenFilePath, - entryPointFilePath, - approveGolden, - stripExportPattern, - typePackageNames, - ); - - if (!succeeded && apiReportChanged) { - console.error(chalk.red(`The API signature has changed and the golden file is outdated.`)); - console.info( - chalk.yellow( - `Golden can be updated by running: yarn bazel run ${process.env.TEST_TARGET}.accept`, - ), - ); - } - - // Bazel expects `3` as exit code for failing tests. - process.exitCode = succeeded ? 0 : 3; -} - -// Invoke main. -(() => { - const args = process.argv.slice(2); - const goldenFilePath = runfiles.resolve(args[0]); - const entryPointFilePath = runfiles.resolve(args[1]); - const approveGolden = args[2] === 'true'; - const stripExportPattern = new RegExp(args[3]); - const typePackageNames = args.slice(4); - - main( - goldenFilePath, - entryPointFilePath, - approveGolden, - stripExportPattern, - typePackageNames, - ).catch((e) => { - console.error(e); - process.exit(1); - }); -})(); diff --git a/bazel/api-golden/index_npm_packages.ts b/bazel/api-golden/index_npm_packages.ts index 67c9b886d..3e8cc6a35 100644 --- a/bazel/api-golden/index_npm_packages.ts +++ b/bazel/api-golden/index_npm_packages.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {findEntryPointsWithinNpmPackage} from './find_entry_points'; -import {join} from 'path'; -import {normalizePathToPosix} from './path-normalize'; +import {findEntryPointsWithinNpmPackage} from './find_entry_points.js'; +import * as path from 'path'; +import {normalizePathToPosix} from './path-normalize.js'; import {readFileSync} from 'fs'; -import {runfiles} from '@bazel/runfiles'; -import {testApiGolden} from './test_api_report'; +import {testApiGolden} from './test_api_report.js'; +import * as fs from 'fs'; /** Interface describing contents of a `package.json`. */ export interface PackageJson { @@ -37,7 +37,7 @@ async function main( // guaranteed to be ESM-only and supports the `mts` extension. const chalk = {red: (v: string) => v, yellow: (v: string) => v}; - const packageJsonPath = join(npmPackageDir, 'package.json'); + const packageJsonPath = path.join(npmPackageDir, 'package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as PackageJson; const entryPoints = findEntryPointsWithinNpmPackage(npmPackageDir, packageJson); const outdatedGoldens: string[] = []; @@ -49,26 +49,34 @@ async function main( // entry-point we maintain a separate golden file. These golden files are // based on the name of the defining NodeJS exports subpath in the NPM package, // See: https://api-extractor.com/pages/overview/demo_api_report/. - const goldenName = join(subpath, 'index.api.md'); - const goldenFilePath = join(goldenDir, goldenName); - const moduleName = normalizePathToPosix(join(packageJson.name, subpath)); + const goldenName = path.join(subpath, 'index.api.md'); + const goldenFilePath = path.join(goldenDir, goldenName); + const moduleName = normalizePathToPosix(path.join(packageJson.name, subpath)); - const {succeeded, apiReportChanged} = await testApiGolden( - goldenFilePath, + const expected = fs.readFileSync(goldenFilePath, 'utf8'); + const actual = await testApiGolden( typesEntryPointPath, - approveGolden, stripExportPattern, typePackageNames, packageJsonPath, moduleName, ); + if (actual === null) { + console.error(`Could not generate API golden for subpath: "${subpath}". See errors above.`); + process.exit(1); + } + // Keep track of outdated goldens. - if (!succeeded && apiReportChanged) { - outdatedGoldens.push(goldenName); + if (actual !== expected) { + if (approveGolden) { + fs.writeFileSync(goldenFilePath, actual, 'utf8'); + } else { + outdatedGoldens.push(goldenName); + } } - allTestsSucceeding = allTestsSucceeding && succeeded; + allTestsSucceeding = allTestsSucceeding && actual === expected; } if (outdatedGoldens.length) { @@ -89,12 +97,17 @@ async function main( // Invoke main. (() => { const args = process.argv.slice(2); - const goldenDir = runfiles.resolve(args[0]); - const npmPackageDir = runfiles.resolve(args[1]); + let goldenDir = path.resolve(args[0]); + const npmPackageDir = path.resolve(args[1]); const approveGolden = args[2] === 'true'; const stripExportPattern = new RegExp(args[3]); const typePackageNames = args.slice(4); + // For approving, point to the real directory outside of the bazel-out. + if (approveGolden) { + goldenDir = path.join(process.env.BUILD_WORKSPACE_DIRECTORY!, args[0]); + } + main(goldenDir, npmPackageDir, approveGolden, stripExportPattern, typePackageNames).catch((e) => { console.error(e); process.exit(1); diff --git a/bazel/api-golden/index_rjs.bzl b/bazel/api-golden/index_rjs.bzl new file mode 100644 index 000000000..0451f6969 --- /dev/null +++ b/bazel/api-golden/index_rjs.bzl @@ -0,0 +1,55 @@ +load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_test") + +default_strip_export_pattern = "^ɵ(?!ɵdefineInjectable|ɵinject|ɵInjectableDef)" + +def _escape_regex_for_arg(value): + """Escapes a Regular expression so that it can be passed as process argument.""" + return "\"%s\"" % value + +def api_golden_test_npm_package( + name, + golden_dir, + npm_package, + data = [], + strip_export_pattern = default_strip_export_pattern, + types = {}, + **kwargs): + """Builds an API report for all entry-points within the given NPM package and compares it. + + Args: + name: Name of the test target + golden_dir: Short path to the golden directory + npm_package: Short path to the NPM package. + data: Runtime dependenices needed for the rule (e.g. the tree artifact of the NPM package) + strip_export_pattern: An optional regular expression to filter out exports from the golden. + types: Optional list of type targets to make available in the API report generation. + **kwargs: Other arguments passed to `js_binary`/`js_test` (depending on approval mode) + """ + + quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern) + + kwargs["tags"] = kwargs.get("tags", []) + ["api_guard"] + + data.append("@devinfra//bazel/api-golden") + + type_names = [] + for type_label, n in types.items(): + data.append(type_label) + type_names.append(n) + + js_test( + name = name, + data = data, + entry_point = "@devinfra//bazel/api-golden:index_npm_packages.js", + args = [golden_dir, npm_package, "false", quoted_export_pattern] + type_names, + **kwargs + ) + + js_binary( + name = name + ".accept", + testonly = True, + data = data, + entry_point = "@devinfra//bazel/api-golden:index_npm_packages.js", + args = [golden_dir, npm_package, "true", quoted_export_pattern] + type_names, + **kwargs + ) diff --git a/bazel/api-golden/module_mappings.ts b/bazel/api-golden/module_mappings.ts index 020ed5bd0..0d07beec6 100644 --- a/bazel/api-golden/module_mappings.ts +++ b/bazel/api-golden/module_mappings.ts @@ -9,8 +9,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import {runfiles} from '@bazel/runfiles'; - /** Regular expression that matches a scoped type package name. */ const scopedTypesPackageRegex = /^@types\/([^_\/]+)__(.+)/; @@ -45,7 +43,9 @@ async function resolveTypeDeclarationOfPackage(moduleName: string): Promise<{ entryPointTypeFile: string; resolvedPackageDir: string; }> { - const pkgJsonPath = runfiles.resolve(`npm/node_modules/${moduleName}/package.json`); + // We are always executing inside a `.runfiles` directory, inside the workspace directory, + // so we can access the repository by walking up one directory. + const pkgJsonPath = path.resolve('../npm/node_modules/' + moduleName + '/package.json'); const pkgJson = JSON.parse(await fs.promises.readFile(pkgJsonPath, 'utf8')) as { types?: string; typings?: string; diff --git a/bazel/api-golden/test/BUILD.bazel b/bazel/api-golden/test/BUILD.bazel index 590384771..4aa6f44a3 100644 --- a/bazel/api-golden/test/BUILD.bazel +++ b/bazel/api-golden/test/BUILD.bazel @@ -1,5 +1,5 @@ -load("//bazel/api-golden:index.bzl", "api_golden_test", "api_golden_test_npm_package") load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") +load("//bazel/api-golden:index.bzl", "api_golden_test", "api_golden_test_npm_package") package(default_visibility = ["//bazel/api-golden/test:__pkg__"]) @@ -16,11 +16,11 @@ copy_to_bin( api_golden_test( name = "test_explicit_files", data = [ - "goldens/test_golden.api.md", + "goldens/explicit_file/index.api.md", "//bazel/api-golden/test/fixtures:test_lib", ], - entry_point = "dev-infra/bazel/api-golden/test/fixtures/test_fixture.d.ts", - golden = "dev-infra/bazel/api-golden/test/goldens/test_golden.api.md", + entry_point = "./fixtures/test_fixture.d.ts", + golden = "dev-infra/bazel/api-golden/test/goldens/explicit_file", # API extractor type resolution is prone to non-sandbox errors, so we test Windows. tags = ["windows"], types = ["@npm//@types/node"], diff --git a/bazel/api-golden/test/goldens/test_golden.api.md b/bazel/api-golden/test/goldens/explicit_file/index.api.md similarity index 90% rename from bazel/api-golden/test/goldens/test_golden.api.md rename to bazel/api-golden/test/goldens/explicit_file/index.api.md index b6c8b29a6..d499964b1 100644 --- a/bazel/api-golden/test/goldens/test_golden.api.md +++ b/bazel/api-golden/test/goldens/explicit_file/index.api.md @@ -1,4 +1,4 @@ -## API Report File for "@angular/build-tooling" +## API Report File for "test_explicit_files" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/bazel/api-golden/test_api_report.ts b/bazel/api-golden/test_api_report.ts index c5243191e..2f0aed6c6 100644 --- a/bazel/api-golden/test_api_report.ts +++ b/bazel/api-golden/test_api_report.ts @@ -13,17 +13,16 @@ import { ExtractorLogLevel, ExtractorMessage, ExtractorMessageId, - ExtractorResult, IConfigFile, } from '@microsoft/api-extractor'; import fs from 'fs'; import path from 'path'; +import os from 'os'; -import {runfiles} from '@bazel/runfiles'; import {AstModule} from '@microsoft/api-extractor/lib/analyzer/AstModule'; import {ExportAnalyzer} from '@microsoft/api-extractor/lib/analyzer/ExportAnalyzer'; -import {resolveTypePackages} from './module_mappings'; -import {patchHostToSkipNodeModules} from './patch-host'; +import {resolveTypePackages} from './module_mappings.js'; +import {patchHostToSkipNodeModules} from './patch-host.js'; /** * Original definition of the `ExportAnalyzer#fetchAstModuleExportInfo` method. @@ -38,7 +37,6 @@ const _origFetchAstModuleExportInfo = ExportAnalyzer.prototype.fetchAstModuleExp * * @param goldenFilePath Path to an API report file that is used as golden * @param indexFilePath Entry point file that is analyzed to build the API report. - * @param approveGolden Whether the golden file should be updated. * @param stripExportPattern Regular Expression that can be used to filter out exports * from the API report. * @param typePackageNames Package names for which types should be included in the analysis of the @@ -51,17 +49,14 @@ const _origFetchAstModuleExportInfo = ExportAnalyzer.prototype.fetchAstModuleExp * is scoped to a specific subpath/entry-point. */ export async function testApiGolden( - goldenFilePath: string, indexFilePath: string, - approveGolden: boolean, stripExportPattern: RegExp, - typePackageNames: string[] = [], - packageJsonPath = resolveWorkspacePackageJsonPath(), - customPackageName?: string, -): Promise { - // If no `TEST_TMPDIR` is defined, then this script runs using `bazel run`. We use - // the runfile directory as temporary directory for API extractor. - const tempDir = process.env.TEST_TMPDIR ?? process.cwd(); + typePackageNames: string[], + packageJsonPath: string, + customPackageName: string, +): Promise { + const tempDir = + process.env.TEST_TMPDIR ?? fs.mkdtempSync(path.join(os.tmpdir(), 'api-golden-rule')); const {paths, typeFiles} = await resolveTypePackages(typePackageNames); const configObject: IConfigFile = { @@ -87,9 +82,9 @@ export async function testApiGolden( docModel: {enabled: false}, apiReport: { enabled: true, - reportFolder: path.dirname(goldenFilePath), + reportFolder: tempDir, reportTempFolder: tempDir, - reportFileName: path.basename(goldenFilePath), + reportFileName: customPackageName.replace(/\//g, '_'), }, tsdocMetadata: {enabled: false}, newlineKind: 'lf', @@ -104,11 +99,7 @@ export async function testApiGolden( }, }; - // We read the specified `package.json` manually and build a package name that is - // compatible with the API extractor. This is a workaround for a bug in api-extractor. - // TODO remove once https://github.com/microsoft/rushstack/issues/2774 is resolved. - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as {name: string}; - const packageNameSegments = (customPackageName ?? packageJson.name).split('/'); + const packageNameSegments = customPackageName.split('/'); const isScopedPackage = packageNameSegments[0][0] === '@' && packageNameSegments.length > 1; // API extractor allows one-slash when the package uses the scoped-package convention. const slashConversionStartIndex = isScopedPackage ? 1 : 0; @@ -145,13 +136,19 @@ export async function testApiGolden( return info; }; - return Extractor.invoke(extractorConfig, { - // If the golden should be approved, then `localBuild: true` instructs - // API extractor to update the file. - localBuild: approveGolden, + const reportTmpOutPath = path.join(tempDir, `${configObject.apiReport!.reportFileName!}.api.md`); + const result = Extractor.invoke(extractorConfig, { + localBuild: true, // Process messages from the API extractor (and modify log levels if needed). - messageCallback: (msg) => processExtractorMessage(msg, approveGolden), + messageCallback: (msg) => processExtractorMessage(msg), }); + + if (!result.succeeded) { + return null; + } + const reportOut = fs.readFileSync(reportTmpOutPath, 'utf8'); + fs.rmSync(reportTmpOutPath); + return reportOut; } /** @@ -159,7 +156,7 @@ export async function testApiGolden( * handle messages before API extractor prints them. This allows us to adjust log level * for certain messages, or to fully prevent messages from being printed out. * */ -async function processExtractorMessage(message: ExtractorMessage, isApprove: boolean) { +async function processExtractorMessage(message: ExtractorMessage) { // If the golden does not match, we hide the error as API extractor prints // a warning asking the user to manually copy the new API report. We print // a custom warning below asking the developer to run the `.accept` Bazel target. @@ -168,12 +165,11 @@ async function processExtractorMessage(message: ExtractorMessage, isApprove: boo // Mark the message as handled so that API-extractor does not print it. We print // a message manually after extraction. message.handled = true; - message.logLevel = isApprove ? ExtractorLogLevel.None : ExtractorLogLevel.Error; + message.logLevel = ExtractorLogLevel.None; } } /** Resolves the `package.json` of the workspace executing this action. */ function resolveWorkspacePackageJsonPath(): string { - const workspaceName = process.env.BAZEL_WORKSPACE!; - return runfiles.resolve(`${workspaceName}/package.json`); + return path.resolve(`./package.json`); } From a323273a38b491ab6863d07a863046d5f66e237c Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 13 Feb 2025 07:41:07 +0000 Subject: [PATCH 4/4] build: remove windows testing and CI setup NOTE: This does not mean we are no longer supporting contributing to Angular on a Windows machine. Instead, we are working towards having a more stable and guaranteed developer experience on Windows! We will still ensure and verify that the Angular CLI (`ng`) works on Windows! --- Many contributors and team members are using Linux or MacOS for development of Angular. Our tooling regularly faces challenges with infrastructure needing extra work and tricks just to function to some extent on Windows. E.g. shell scripts or Bazel actions that need extra layers for batch files that spawn a Bash shell etc, or inconsistent escaping across shells. This is putting extra stress on our infrastructure team, results in in unnecessary complexity and slows down overall development that way. There is a simple and well-proven solution for supporting Windows development. Using WSL2, developes have access to a Bash shell that is equivalent to Linux; which we use on a day to day basis and can guarantee that development works smoothly! This means, we'd be able to offer far better stability and guarantees to our Windows contributors. Historically, we also haven't seen a lot of Angular contributors use Windows; and if they did; they already used WSL for development. At this point we don't have any non-WSL contributors anymore (in the team or known), so we don't know anything about how well development works via e.g. Git Bash on Windows. Testing on CI via Windows WSL is not necessary as the WSL shell is expected to be the same as the Linux bash environment. We'll be able to revisit this if we have new learnings. There are ways to execute builds etc. via WSL on CI. --- .github/workflows/pr.yml | 14 -------------- WORKSPACE | 1 - bazel/api-golden/test/BUILD.bazel | 6 ------ bazel/browsers/test/BUILD.bazel | 1 - bazel/git-toolchain/BUILD.bazel | 16 +--------------- .../tests/package_mappings/BUILD.bazel | 5 ----- 6 files changed, 1 insertion(+), 42 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 495752618..023accd47 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -56,20 +56,6 @@ jobs: - run: yarn install --immutable - run: yarn bazel test -- //... - test-win: - timeout-minutes: 30 - runs-on: windows-latest - steps: - # Because the checkout and setup node action is contained in the dev-infra repo, we must - # checkout the repo to be able to run the action we have created. Other repos will skip - # this step. - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: ./github-actions/npm/checkout-and-setup-node - - uses: ./github-actions/bazel/setup - - uses: ./github-actions/bazel/configure-remote - - run: yarn install --immutable - - run: yarn bazel test --test_tag_filters=windows --build_tests_only -- ... -bazel/remote-execution/... - test-macos: timeout-minutes: 30 runs-on: macos-latest diff --git a/WORKSPACE b/WORKSPACE index 7e20e629b..a0c916cbf 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -136,7 +136,6 @@ register_toolchains( "//bazel/git-toolchain:git_linux_toolchain", "//bazel/git-toolchain:git_macos_x86_toolchain", "//bazel/git-toolchain:git_macos_arm64_toolchain", - "//bazel/git-toolchain:git_windows_toolchain", ) http_file( diff --git a/bazel/api-golden/test/BUILD.bazel b/bazel/api-golden/test/BUILD.bazel index 4aa6f44a3..bf555a2f4 100644 --- a/bazel/api-golden/test/BUILD.bazel +++ b/bazel/api-golden/test/BUILD.bazel @@ -21,8 +21,6 @@ api_golden_test( ], entry_point = "./fixtures/test_fixture.d.ts", golden = "dev-infra/bazel/api-golden/test/goldens/explicit_file", - # API extractor type resolution is prone to non-sandbox errors, so we test Windows. - tags = ["windows"], types = ["@npm//@types/node"], ) @@ -34,8 +32,6 @@ api_golden_test_npm_package( ], golden_dir = "dev-infra/bazel/api-golden/test/goldens/test_package", npm_package = "dev-infra/bazel/api-golden/test/fixtures/test_package", - # API extractor type resolution is prone to non-sandbox errors, so we test Windows. - tags = ["windows"], # API extractor needs to be able to resolve `@babel/core` due to an aliased namespace # we expose as part of the `nested.d.ts` fake entry-point. types = ["@npm//@types/babel__core"], @@ -49,6 +45,4 @@ api_golden_test_npm_package( ], golden_dir = "dev-infra/bazel/api-golden/test/goldens/pkg_no_exports_field", npm_package = "dev-infra/bazel/api-golden/test/fixtures/pkg_no_exports_field", - # API extractor type resolution is prone to non-sandbox errors, so we test Windows. - tags = ["windows"], ) diff --git a/bazel/browsers/test/BUILD.bazel b/bazel/browsers/test/BUILD.bazel index 02570669b..a1ed8487a 100644 --- a/bazel/browsers/test/BUILD.bazel +++ b/bazel/browsers/test/BUILD.bazel @@ -11,7 +11,6 @@ karma_web_test_suite( tags = [ "linux", "macos", - "windows", ], deps = [ ":test_bundle", diff --git a/bazel/git-toolchain/BUILD.bazel b/bazel/git-toolchain/BUILD.bazel index dd63f584d..83ce0ab16 100644 --- a/bazel/git-toolchain/BUILD.bazel +++ b/bazel/git-toolchain/BUILD.bazel @@ -1,5 +1,5 @@ -load(":toolchain.bzl", "git_toolchain") load(":alias.bzl", "git_toolchain_alias") +load(":toolchain.bzl", "git_toolchain") package(default_visibility = ["//visibility:public"]) @@ -74,17 +74,3 @@ toolchain( toolchain = ":git_macos_arm64", toolchain_type = ":toolchain_type", ) - -toolchain( - name = "git_windows_toolchain", - exec_compatible_with = [ - "@platforms//os:windows", - "@platforms//cpu:x86_64", - ], - target_compatible_with = [ - "@platforms//os:windows", - "@platforms//cpu:x86_64", - ], - toolchain = ":git_windows", - toolchain_type = ":toolchain_type", -) diff --git a/bazel/integration/tests/package_mappings/BUILD.bazel b/bazel/integration/tests/package_mappings/BUILD.bazel index 7eb0c946b..290590982 100644 --- a/bazel/integration/tests/package_mappings/BUILD.bazel +++ b/bazel/integration/tests/package_mappings/BUILD.bazel @@ -13,11 +13,6 @@ integration_test( npm_packages = { "//bazel/integration/tests/package_mappings/fake_pkg_srcs:archive": "fake_pkg", }, - tags = [ - # We want to run a basic integration test on Windows to ensure the integration - # test rule works properly for Windows contributors. - "windows", - ], tool_mappings = { "//:yarn_classic_vendored": "yarn", "@nodejs_toolchains//:resolved_toolchain": "node",