Skip to content

Commit a6449b7

Browse files
authored
feat: add generate_local_modules_build_files flag to yarn_install and npm_install rules (#2449)
1 parent bc7dc82 commit a6449b7

File tree

4 files changed

+41
-58
lines changed

4 files changed

+41
-58
lines changed

WORKSPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ yarn_install(
203203
environment = {
204204
"SOME_USER_ENV": "yarn is great!",
205205
},
206+
generate_local_modules_build_files = False,
206207
included_files = [
207208
"",
208209
".js",
@@ -226,6 +227,7 @@ npm_install(
226227
environment = {
227228
"SOME_USER_ENV": "npm is cool!",
228229
},
230+
generate_local_modules_build_files = False,
229231
included_files = [
230232
"",
231233
".js",

internal/npm_install/generate_build_file.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ const WORKSPACE_ROOT_PREFIX = args[4];
5757
const WORKSPACE_ROOT_BASE = WORKSPACE_ROOT_PREFIX ?.split('/')[0];
5858
const STRICT_VISIBILITY = args[5]?.toLowerCase() === 'true';
5959
const INCLUDED_FILES = args[6] ? args[6].split(',') : [];
60-
const BAZEL_VERSION = args[7];
61-
const PACKAGE_PATH = args[8];
60+
const GENERATE_LOCAL_MODULES_BUILD_FILES = (`${args[7]}`.toLowerCase()) === 'true';
61+
const BAZEL_VERSION = args[8];
62+
const PACKAGE_PATH = args[9];
6263

6364
const PUBLIC_VISIBILITY = '//visibility:public';
6465
const LIMITED_VISIBILITY = `@${WORKSPACE}//:__subpackages__`;
@@ -221,18 +222,12 @@ function generatePackageBuildFiles(pkg: Dep) {
221222
// install a node_module with link:)
222223
const isPkgDirASymlink =
223224
fs.existsSync(nodeModulesPkgDir) && fs.lstatSync(nodeModulesPkgDir).isSymbolicLink();
224-
// Check if the current package is also written inside the workspace
225-
// NOTE: It's a corner case but fs.realpathSync(.) will not be the root of
226-
// the workspace if symlink_node_modules = False as yarn & npm are run in the root of the
227-
// external repository and anything linked with a relative path would have to be copied
228-
// over via that data attribute
229-
const isPkgInsideWorkspace = fs.realpathSync(nodeModulesPkgDir).includes(fs.realpathSync(`.`));
230225
// Mark build file as one to symlink instead of generate as the package dir is a symlink, we
231226
// have a BUILD file and the pkg is written inside the workspace
232-
const symlinkBuildFile = isPkgDirASymlink && buildFilePath && isPkgInsideWorkspace;
227+
const symlinkBuildFile = isPkgDirASymlink && buildFilePath && !GENERATE_LOCAL_MODULES_BUILD_FILES;
233228

234229
// Log if a BUILD file was expected but was not found
235-
if (!symlinkBuildFile && isPkgDirASymlink) {
230+
if (isPkgDirASymlink && !buildFilePath && !GENERATE_LOCAL_MODULES_BUILD_FILES) {
236231
console.log(`[yarn_install/npm_install]: package ${
237232
nodeModulesPkgDir} is local symlink and as such a BUILD file for it is expected but none was found. Please add one at ${
238233
fs.realpathSync(nodeModulesPkgDir)}`)

internal/npm_install/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ const WORKSPACE_ROOT_PREFIX = args[4];
1717
const WORKSPACE_ROOT_BASE = (_a = WORKSPACE_ROOT_PREFIX) === null || _a === void 0 ? void 0 : _a.split('/')[0];
1818
const STRICT_VISIBILITY = ((_b = args[5]) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 'true';
1919
const INCLUDED_FILES = args[6] ? args[6].split(',') : [];
20-
const BAZEL_VERSION = args[7];
21-
const PACKAGE_PATH = args[8];
20+
const GENERATE_LOCAL_MODULES_BUILD_FILES = (`${args[7]}`.toLowerCase()) === 'true';
21+
const BAZEL_VERSION = args[8];
22+
const PACKAGE_PATH = args[9];
2223
const PUBLIC_VISIBILITY = '//visibility:public';
2324
const LIMITED_VISIBILITY = `@${WORKSPACE}//:__subpackages__`;
2425
function generateBuildFileHeader(visibility = PUBLIC_VISIBILITY) {
@@ -123,9 +124,8 @@ function generatePackageBuildFiles(pkg) {
123124
buildFilePath = 'BUILD.bazel';
124125
const nodeModulesPkgDir = `node_modules/${pkg._dir}`;
125126
const isPkgDirASymlink = fs.existsSync(nodeModulesPkgDir) && fs.lstatSync(nodeModulesPkgDir).isSymbolicLink();
126-
const isPkgInsideWorkspace = fs.realpathSync(nodeModulesPkgDir).includes(fs.realpathSync(`.`));
127-
const symlinkBuildFile = isPkgDirASymlink && buildFilePath && isPkgInsideWorkspace;
128-
if (!symlinkBuildFile && isPkgDirASymlink) {
127+
const symlinkBuildFile = isPkgDirASymlink && buildFilePath && !GENERATE_LOCAL_MODULES_BUILD_FILES;
128+
if (isPkgDirASymlink && !buildFilePath && !GENERATE_LOCAL_MODULES_BUILD_FILES) {
129129
console.log(`[yarn_install/npm_install]: package ${nodeModulesPkgDir} is local symlink and as such a BUILD file for it is expected but none was found. Please add one at ${fs.realpathSync(nodeModulesPkgDir)}`);
130130
}
131131
let buildFile = printPackage(pkg);

internal/npm_install/npm_install.bzl

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,29 @@ repository so all files that the package manager depends on must be listed.
4444
doc = """Environment variables to set before calling the package manager.""",
4545
default = {},
4646
),
47+
"generate_local_modules_build_files": attr.bool(
48+
default = True,
49+
doc = """Enables the BUILD files auto generation for local modules installed with `file:` (npm) or `link:` (yarn)
50+
51+
When using a monorepo it's common to have modules that we want to use locally and
52+
publish to an external package repository. This can be achieved using a `js_library` rule
53+
with a `package_name` attribute defined inside the local package `BUILD` file. However,
54+
if the project relies on the local package dependency with `file:` (npm) or `link:` (yarn) to be used outside Bazel, this
55+
could introduce a race condition with both `npm_install` or `yarn_install` rules.
56+
57+
In order to overcome it, a link could be created to the package `BUILD` file from the
58+
npm external Bazel repository (so we can use a local BUILD file instead of an auto generated one),
59+
which require us to set `generate_local_modules_build_files = False` and complete a last step which is writing the
60+
expected targets on that same `BUILD` file to be later used both by `npm_install` or `yarn_install`
61+
rules, which are: `<package_name__files>`, `<package_name__nested_node_modules>`,
62+
`<package_name__contents>`, `<package_name__typings>` and the last one just `<package_name>`. If you doubt what those targets
63+
should look like, check the generated `BUILD` file for a given node module.
64+
65+
When true, the rule will follow the default behaviour of auto generating BUILD files for each `node_module` at install time.
66+
67+
When False, the rule will not auto generate BUILD files for `node_modules` that are installed as symlinks for local modules.
68+
""",
69+
),
4770
"included_files": attr.string_list(
4871
doc = """List of file extensions to be included in the npm package targets.
4972
@@ -123,7 +146,7 @@ data attribute.
123146
),
124147
})
125148

126-
def _create_build_files(repository_ctx, rule_type, node, lock_file):
149+
def _create_build_files(repository_ctx, rule_type, node, lock_file, generate_local_modules_build_files):
127150
repository_ctx.report_progress("Processing node_modules: installing Bazel packages and generating BUILD files")
128151
if repository_ctx.attr.manual_build_file_contents:
129152
repository_ctx.file("manual_build_file_contents", repository_ctx.attr.manual_build_file_contents)
@@ -137,6 +160,7 @@ def _create_build_files(repository_ctx, rule_type, node, lock_file):
137160
_workspace_root_prefix(repository_ctx),
138161
str(repository_ctx.attr.strict_visibility),
139162
",".join(repository_ctx.attr.included_files),
163+
str(generate_local_modules_build_files),
140164
native.bazel_version,
141165
repository_ctx.attr.package_path,
142166
# double the default timeout in case of many packages, see #2231
@@ -344,7 +368,7 @@ cd /D "{root}" && "{npm}" {npm_args}
344368

345369
_symlink_node_modules(repository_ctx)
346370

347-
_create_build_files(repository_ctx, "npm_install", node, repository_ctx.attr.package_lock_json)
371+
_create_build_files(repository_ctx, "npm_install", node, repository_ctx.attr.package_lock_json, repository_ctx.attr.generate_local_modules_build_files)
348372

349373
npm_install = repository_rule(
350374
attrs = dict(COMMON_ATTRIBUTES, **{
@@ -374,26 +398,7 @@ See npm CLI docs https://docs.npmjs.com/cli/install.html for complete list of su
374398
375399
This rule will set the environment variable `BAZEL_NPM_INSTALL` to '1' (unless it
376400
set to another value in the environment attribute). Scripts may use to this to
377-
check if yarn is being run by the `npm_install` repository rule.
378-
379-
380-
**LOCAL MODULES WITH THE NEED TO BE USED BOTH INSIDE AND OUTSIDE BAZEL**
381-
382-
When using a monorepo it's common to have modules that we want to use locally and
383-
publish to an external package repository. This can be achieved using a `js_library` rule
384-
with a `package_name` attribute defined inside the local package `BUILD` file. However,
385-
if the project relies on the local package dependency with `file:`, this could introduce a
386-
race condition with the `npm_install` rule.
387-
388-
In order to overcome it, a link will be created to the package `BUILD` file from the
389-
npm external Bazel repository, which require us to complete a last step which is writing
390-
the expected targets on that same `BUILD` file to be later used by the `npm_install`
391-
rule, which are: `<package_name__files>`, `<package_name__nested_node_modules>`,
392-
`<package_name__contents>`, `<package_name__typings>` and the last
393-
one just `<package_name>`.
394-
395-
If you doubt what those targets should look like, check the
396-
generated `BUILD` file for a given node module.""",
401+
check if yarn is being run by the `npm_install` repository rule.""",
397402
implementation = _npm_install_impl,
398403
)
399404

@@ -499,7 +504,7 @@ cd /D "{root}" && "{yarn}" {yarn_args}
499504

500505
_symlink_node_modules(repository_ctx)
501506

502-
_create_build_files(repository_ctx, "yarn_install", node, repository_ctx.attr.yarn_lock)
507+
_create_build_files(repository_ctx, "yarn_install", node, repository_ctx.attr.yarn_lock, repository_ctx.attr.generate_local_modules_build_files)
503508

504509
yarn_install = repository_rule(
505510
attrs = dict(COMMON_ATTRIBUTES, **{
@@ -550,25 +555,6 @@ to yarn so that the local cache is contained within the external repository.
550555
551556
This rule will set the environment variable `BAZEL_YARN_INSTALL` to '1' (unless it
552557
set to another value in the environment attribute). Scripts may use to this to
553-
check if yarn is being run by the `yarn_install` repository rule.
554-
555-
556-
**LOCAL MODULES WITH THE NEED TO BE USED BOTH INSIDE AND OUTSIDE BAZEL**
557-
558-
When using a monorepo it's common to have modules that we want to use locally and
559-
publish to an external package repository. This can be achieved using a `js_library` rule
560-
with a `package_name` attribute defined inside the local package `BUILD` file. However,
561-
if the project relies on the local package dependency with `link:`, this could introduce a
562-
race condition with the `yarn_install` rule.
563-
564-
In order to overcome it, a link will be created to the package `BUILD` file from the
565-
npm external Bazel repository, which require us to complete a last step which is writing
566-
the expected targets on that same `BUILD` file to be later used by the `yarn_install`
567-
rule, which are: `<package_name__files>`, `<package_name__nested_node_modules>`,
568-
`<package_name__contents>`, `<package_name__typings>` and the last
569-
one just `<package_name>`.
570-
571-
If you doubt what those targets should look like, check the
572-
generated `BUILD` file for a given node module.""",
558+
check if yarn is being run by the `yarn_install` repository rule.""",
573559
implementation = _yarn_install_impl,
574560
)

0 commit comments

Comments
 (0)