Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create symlink for build files present on node modules installed with relative paths #2330

Merged
Merged
6 changes: 6 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ local_repository(
yarn_install(
name = "fine_grained_deps_yarn",
data = [
"//:tools/npm_packages/local_module/yarn/BUILD.bazel",
"//:tools/npm_packages/local_module/yarn/index.js",
"//:tools/npm_packages/local_module/yarn/package.json",
"//internal/npm_install/test:postinstall.js",
],
environment = {
Expand All @@ -209,6 +212,9 @@ yarn_install(
npm_install(
name = "fine_grained_deps_npm",
data = [
"//:tools/npm_packages/local_module/npm/BUILD.bazel",
"//:tools/npm_packages/local_module/npm/index.js",
"//:tools/npm_packages/local_module/npm/package.json",
"//internal/npm_install/test:postinstall.js",
],
environment = {
Expand Down
36 changes: 36 additions & 0 deletions docs/Built-ins.md
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,24 @@ Defaults to `True`

Defaults to `3600`

**LOCAL MODULES**
mistic marked this conversation as resolved.
Show resolved Hide resolved

When using a monorepo is common to have locally written modules that we both
want to use locally while publicly publishing them. To achieve it, we need
to declare the package with `file:` in the monorepo `package.json` and
define a `BUILD` file for that local package with a `js_library` rule
defining its `package_name` argument.

Doing what is mentioned above will be creating a link to the previous
created `BUILD` file from the npm external Bazel repository, which require
us to complete a last step which writing the expected targets on that
same `BUILD` file to be later used by the `npm_install` rule, which
are: `<package_name__files>`, `<package_name__nested_node_modules>`,
`<package_name__contents>`, `<package_name__typings>` and the last
one just `<package_name>`.

If you doubt what those targets should look like, check the
generated `BUILD` file for a given node module.


## pkg_npm
Expand Down Expand Up @@ -1314,6 +1332,24 @@ Defaults to `True`
(*<a href="https://bazel.build/docs/build-ref.html#labels">Label</a>, mandatory*)


**LOCAL MODULES**

When using a monorepo is common to have locally written modules that we both
want to use locally while publicly publishing them. To achieve it, we need
to declare the package with `link:` in the monorepo `package.json` and
define a `BUILD` file for that local package with a `js_library` rule
defining its `package_name` argument.

Doing what is mentioned above will be creating a link to the previous
created `BUILD` file from the npm external Bazel repository, which require
us to complete a last step which writing the expected targets on that
same `BUILD` file to be later used by the `yarn_install` rule, which
are: `<package_name__files>`, `<package_name__nested_node_modules>`,
`<package_name__contents>`, `<package_name__typings>` and the last
one just `<package_name>`.

If you doubt what those targets should look like, check the
generated `BUILD` file for a given node module.


## check_bazel_version
Expand Down
48 changes: 45 additions & 3 deletions internal/npm_install/generate_build_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ function writeFileSync(p: string, content: string) {
fs.writeFileSync(p, content);
}

/**
* Creates a file symlink, first ensuring that the directory to
* create it into exists.
*/
function createFileSymlinkSync(target: string, p: string) {
mkdirp(path.dirname(p));
fs.symlinkSync(target, p, 'file');
}

/**
* Main entrypoint.
*/
Expand Down Expand Up @@ -194,11 +203,37 @@ js_library(
* Generates all BUILD & bzl files for a package.
*/
function generatePackageBuildFiles(pkg: Dep) {
// If a BUILD file was shipped with the package, append its contents to the end of
// what we generate for the package.
// If a BUILD file was shipped with the package we should symlink the generated BUILD file
// instead of append its contents to the end of the one we were going to generate.
// https://github.com/bazelbuild/rules_nodejs/issues/2131
let buildFilePath: string|undefined;
if (pkg._files.includes('BUILD')) buildFilePath = 'BUILD';
if (pkg._files.includes('BUILD.bazel')) buildFilePath = 'BUILD.bazel';

// Recreate the pkg dir inside the node_modules folder
const nodeModulesPkgDir = `node_modules/${pkg._dir}`;
// Check if the current package dep dir is a symlink (which happens when we
// install a node_module with link:)
const isPkgDirASymlink =
fs.existsSync(nodeModulesPkgDir) && fs.lstatSync(nodeModulesPkgDir).isSymbolicLink();
// Check if the current package is also written inside the workspace
// NOTE: It's a corner case but fs.realpathSync(.) will not be the root of
// the workspace if symlink_node_modules = False as yarn & npm are run in the root of the
// external repository and anything linked with a relative path would have to be copied
// over via that data attribute
const isPkgInsideWorkspace = fs.realpathSync(nodeModulesPkgDir).includes(fs.realpathSync(`.`));
mistic marked this conversation as resolved.
Show resolved Hide resolved
// Mark build file as one to symlink instead of generate as the package dir is a symlink, we
// have a BUILD file and the pkg is written inside the workspace
const symlinkBuildFile = isPkgDirASymlink && buildFilePath && isPkgInsideWorkspace;

// Log if a BUILD file was expected but was not found
if (!symlinkBuildFile && isPkgDirASymlink) {
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)}`)
}

// The following won't be used in a symlink build file case
let buildFile = printPackage(pkg);
if (buildFilePath) {
buildFile = buildFile + '\n' +
Expand Down Expand Up @@ -256,7 +291,14 @@ exports_files(["index.bzl"])
}
}

writeFileSync(path.posix.join(pkg._dir, buildFilePath), generateBuildFileHeader(visibility) + buildFile);
if (!symlinkBuildFile) {
writeFileSync(
path.posix.join(pkg._dir, buildFilePath), generateBuildFileHeader(visibility) + buildFile);
} else {
const realPathBuildFileForPkg =
fs.realpathSync(path.posix.join(nodeModulesPkgDir, buildFilePath));
createFileSymlinkSync(realPathBuildFileForPkg, path.posix.join(pkg._dir, buildFilePath));
}
}

/**
Expand Down
19 changes: 18 additions & 1 deletion internal/npm_install/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ function writeFileSync(p, content) {
mkdirp(path.dirname(p));
fs.writeFileSync(p, content);
}
function createFileSymlinkSync(target, p) {
mkdirp(path.dirname(p));
fs.symlinkSync(target, p, 'file');
}
function main() {
const deps = getDirectDependencySet(PKG_JSON_FILE_PATH);
const pkgs = findPackages('node_modules', deps);
Expand Down Expand Up @@ -113,6 +117,13 @@ function generatePackageBuildFiles(pkg) {
buildFilePath = 'BUILD';
if (pkg._files.includes('BUILD.bazel'))
buildFilePath = 'BUILD.bazel';
const nodeModulesPkgDir = `node_modules/${pkg._dir}`;
const isPkgDirASymlink = fs.existsSync(nodeModulesPkgDir) && fs.lstatSync(nodeModulesPkgDir).isSymbolicLink();
const isPkgInsideWorkspace = fs.realpathSync(nodeModulesPkgDir).includes(fs.realpathSync(`.`));
const symlinkBuildFile = isPkgDirASymlink && buildFilePath && isPkgInsideWorkspace;
if (!symlinkBuildFile && isPkgDirASymlink) {
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)}`);
}
let buildFile = printPackage(pkg);
if (buildFilePath) {
buildFile = buildFile + '\n' +
Expand Down Expand Up @@ -154,7 +165,13 @@ exports_files(["index.bzl"])
`;
}
}
writeFileSync(path.posix.join(pkg._dir, buildFilePath), generateBuildFileHeader(visibility) + buildFile);
if (!symlinkBuildFile) {
writeFileSync(path.posix.join(pkg._dir, buildFilePath), generateBuildFileHeader(visibility) + buildFile);
}
else {
const realPathBuildFileForPkg = fs.realpathSync(path.posix.join(nodeModulesPkgDir, buildFilePath));
createFileSymlinkSync(realPathBuildFileForPkg, path.posix.join(pkg._dir, buildFilePath));
}
}
function generateBazelWorkspaces(pkgs) {
const workspaces = {};
Expand Down
1 change: 1 addition & 0 deletions internal/npm_install/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ sh_test(
"@fine_grained_deps_%s//jasmine" % pkgmgr,
"@fine_grained_deps_%s//jasmine-core" % pkgmgr,
"@fine_grained_deps_%s//ajv" % pkgmgr,
"@fine_grained_deps_%s//local-module" % pkgmgr,
"@fine_grained_deps_%s//typescript" % pkgmgr,
"@fine_grained_deps_%s//rxjs" % pkgmgr,
# Note, test-b depends on test-a@0.0.1 which should be
Expand Down
4 changes: 4 additions & 0 deletions internal/npm_install/test/common.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ describe('dependencies', () => {
require('ajv/lib/$data');
});

it(`should resolve local-module`, () => {
require('local-module');
});

it(`should resolve rxjs/src/tsconfig.json`, () => {
// the BUILD.bazel file in rxjs/src should have been
// deleted by fine grained deps and rxjs/src/tsconfig.json
Expand Down
3 changes: 3 additions & 0 deletions tools/fine_grained_deps_npm/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tools/fine_grained_deps_npm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"chokidar": "2.0.4",
"http-server": "github:alexeagle/http-server#97205e945b69091606ed83aa0c8489e9ce65d282",
"klaw": "1.3.1",
"local-module": "file:tools/npm_packages/local_module/npm",
"rxjs": "6.5.0"
},
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions tools/fine_grained_deps_yarn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"chokidar": "2.0.4",
"http-server": "github:alexeagle/http-server#97205e945b69091606ed83aa0c8489e9ce65d282",
"klaw": "1.3.1",
"local-module": "link:tools/npm_packages/local_module/yarn",
"rxjs": "6.5.0"
},
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions tools/fine_grained_deps_yarn/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,10 @@ klaw@1.3.1:
optionalDependencies:
graceful-fs "^4.1.9"

"local-module@link:tools/npm_packages/local_module/yarn":
version "0.0.0"
uid ""

lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
Expand Down
35 changes: 35 additions & 0 deletions tools/npm_packages/local_module/npm/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")

package(default_visibility = ["//visibility:public"])

SRCS = [
"index.js",
"package.json",
]

filegroup(
name = "local-module__files",
srcs = ["@fine_grained_deps_npm//:node_modules/local-module/%s" % file for file in SRCS],
)

js_library(
name = "local-module",
srcs = [":local-module__files"],
deps = [
":local-module__contents",
],
)

js_library(
name = "local-module__contents",
srcs = [
":local-module__files",
":local-module__nested_node_modules",
],
visibility = ["//:__subpackages__"],
)

filegroup(
name = "local-module__nested_node_modules",
visibility = ["//:__subpackages__"],
)
1 change: 1 addition & 0 deletions tools/npm_packages/local_module/npm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'local_module';
4 changes: 4 additions & 0 deletions tools/npm_packages/local_module/npm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"version": "0.0.1",
"main": "index.js"
}
35 changes: 35 additions & 0 deletions tools/npm_packages/local_module/yarn/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")

package(default_visibility = ["//visibility:public"])

SRCS = [
"index.js",
"package.json",
]

filegroup(
name = "local-module__files",
srcs = ["@fine_grained_deps_yarn//:node_modules/local-module/%s" % file for file in SRCS],
)

js_library(
name = "local-module",
srcs = [":local-module__files"],
deps = [
":local-module__contents",
],
)

js_library(
name = "local-module__contents",
srcs = [
":local-module__files",
":local-module__nested_node_modules",
],
visibility = ["//:__subpackages__"],
)

filegroup(
name = "local-module__nested_node_modules",
visibility = ["//:__subpackages__"],
)
1 change: 1 addition & 0 deletions tools/npm_packages/local_module/yarn/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'local_module';
4 changes: 4 additions & 0 deletions tools/npm_packages/local_module/yarn/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"version": "0.0.1",
"main": "index.js"
}