diff --git a/internal/npm_install/generate_build_file.ts b/internal/npm_install/generate_build_file.ts index 57d505a3c3..a7eb14d6a5 100644 --- a/internal/npm_install/generate_build_file.ts +++ b/internal/npm_install/generate_build_file.ts @@ -186,24 +186,63 @@ node_module_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. + let buildFilePath: string|undefined; + if (pkg._files.includes('BUILD')) buildFilePath = 'BUILD'; + if (pkg._files.includes('BUILD.bazel')) buildFilePath = 'BUILD.bazel'; let buildFile = printPackage(pkg); + if (buildFilePath) { + buildFile = buildFile + '\n' + + fs.readFileSync(path.join('node_modules', pkg._dir, buildFilePath), 'utf-8'); + } else { + buildFilePath = 'BUILD.bazel' + } - const binBuildFile = printPackageBin(pkg); - if (binBuildFile.length) { - writeFileSync( - path.posix.join(pkg._dir, 'bin', 'BUILD.bazel'), BUILD_FILE_HEADER + binBuildFile); + // If the package didn't ship a bin/BUILD file, generate one. + if (!pkg._files.includes('bin/BUILD.bazel') && !pkg._files.includes('bin/BUILD')) { + const binBuildFile = printPackageBin(pkg); + if (binBuildFile.length) { + writeFileSync( + path.posix.join(pkg._dir, 'bin', 'BUILD.bazel'), BUILD_FILE_HEADER + binBuildFile); + } } - const indexFile = printIndexBzl(pkg); - if (indexFile.length) { - writeFileSync(path.posix.join(pkg._dir, 'index.bzl'), indexFile); - buildFile = `${buildFile} + // If there's an index.bzl in the package then copy all the package's files + // other than the BUILD file which we'll write below. + // (maybe we shouldn't copy .js though, since it belongs under node_modules?) + if (pkg._files.includes('index.bzl')) { + pkg._files.filter(f => f !== 'BUILD' && f !== 'BUILD.bazel').forEach(file => { + if (/^node_modules[/\\]/.test(file)) { + // don't copy over nested node_modules + return; + } + // don't support rootPath here? + let destFile = path.posix.join(pkg._dir, file); + const basename = path.basename(file); + const basenameUc = basename.toUpperCase(); + // Bazel BUILD files from npm distribution would have been renamed earlier with a _ prefix so + // we restore the name on the copy + if (basenameUc === '_BUILD' || basenameUc === '_BUILD.BAZEL') { + destFile = path.posix.join(path.dirname(destFile), basename.substr(1)); + } + const src = path.posix.join('node_modules', pkg._dir, file); + + mkdirp(path.dirname(destFile)); + fs.copyFileSync(src, destFile); + }); + } else { + const indexFile = printIndexBzl(pkg); + if (indexFile.length) { + writeFileSync(path.posix.join(pkg._dir, 'index.bzl'), indexFile); + buildFile += ` # For integration testing exports_files(["index.bzl"]) `; + } } - writeFileSync(path.posix.join(pkg._dir, 'BUILD.bazel'), BUILD_FILE_HEADER + buildFile); + writeFileSync(path.posix.join(pkg._dir, buildFilePath), BUILD_FILE_HEADER + buildFile); } /** diff --git a/internal/npm_install/index.js b/internal/npm_install/index.js index edc89417fa..f4cc3fce44 100644 --- a/internal/npm_install/index.js +++ b/internal/npm_install/index.js @@ -102,20 +102,52 @@ node_module_library( writeFileSync('BUILD.bazel', buildFile); } function generatePackageBuildFiles(pkg) { + let buildFilePath; + if (pkg._files.includes('BUILD')) + buildFilePath = 'BUILD'; + if (pkg._files.includes('BUILD.bazel')) + buildFilePath = 'BUILD.bazel'; let buildFile = printPackage(pkg); - const binBuildFile = printPackageBin(pkg); - if (binBuildFile.length) { - writeFileSync(path.posix.join(pkg._dir, 'bin', 'BUILD.bazel'), BUILD_FILE_HEADER + binBuildFile); - } - const indexFile = printIndexBzl(pkg); - if (indexFile.length) { - writeFileSync(path.posix.join(pkg._dir, 'index.bzl'), indexFile); - buildFile = `${buildFile} + if (buildFilePath) { + buildFile = buildFile + '\n' + + fs.readFileSync(path.join('node_modules', pkg._dir, buildFilePath), 'utf-8'); + } + else { + buildFilePath = 'BUILD.bazel'; + } + if (!pkg._files.includes('bin/BUILD.bazel') && !pkg._files.includes('bin/BUILD')) { + const binBuildFile = printPackageBin(pkg); + if (binBuildFile.length) { + writeFileSync(path.posix.join(pkg._dir, 'bin', 'BUILD.bazel'), BUILD_FILE_HEADER + binBuildFile); + } + } + if (pkg._files.includes('index.bzl')) { + pkg._files.filter(f => f !== 'BUILD' && f !== 'BUILD.bazel').forEach(file => { + if (/^node_modules[/\\]/.test(file)) { + return; + } + let destFile = path.posix.join(pkg._dir, file); + const basename = path.basename(file); + const basenameUc = basename.toUpperCase(); + if (basenameUc === '_BUILD' || basenameUc === '_BUILD.BAZEL') { + destFile = path.posix.join(path.dirname(destFile), basename.substr(1)); + } + const src = path.posix.join('node_modules', pkg._dir, file); + mkdirp(path.dirname(destFile)); + fs.copyFileSync(src, destFile); + }); + } + else { + const indexFile = printIndexBzl(pkg); + if (indexFile.length) { + writeFileSync(path.posix.join(pkg._dir, 'index.bzl'), indexFile); + buildFile += ` # For integration testing exports_files(["index.bzl"]) `; + } } - writeFileSync(path.posix.join(pkg._dir, 'BUILD.bazel'), BUILD_FILE_HEADER + buildFile); + writeFileSync(path.posix.join(pkg._dir, buildFilePath), BUILD_FILE_HEADER + buildFile); } function generateBazelWorkspaces(pkgs) { const workspaces = {}; diff --git a/internal/npm_install/test/npm_packages/BUILD.bazel b/internal/npm_install/test/npm_packages/BUILD.bazel new file mode 100644 index 0000000000..a5495fa479 --- /dev/null +++ b/internal/npm_install/test/npm_packages/BUILD.bazel @@ -0,0 +1,10 @@ +load("@npm//bazel_workspaces_consistent:index.bzl", "some_rule") +load("@npm_bazel_jasmine//:index.bzl", "jasmine_node_test") + +some_rule(name = "test_data") + +jasmine_node_test( + name = "test", + srcs = ["spec.js"], + data = ["test_data"], +) diff --git a/internal/npm_install/test/npm_packages/README.md b/internal/npm_install/test/npm_packages/README.md new file mode 100644 index 0000000000..08c4161eab --- /dev/null +++ b/internal/npm_install/test/npm_packages/README.md @@ -0,0 +1,5 @@ +These test the installation of npm packages which contain bazel rules. +We call these "hybrid" packages because they're distributed, versioned, and installed by npm +but they contain bazel rules we can call from BUILD files. + +The packages themselves are in /tools/npm_packages/bazel_workspaces* diff --git a/internal/npm_install/test/npm_packages/spec.js b/internal/npm_install/test/npm_packages/spec.js new file mode 100644 index 0000000000..f98b1ca5e0 --- /dev/null +++ b/internal/npm_install/test/npm_packages/spec.js @@ -0,0 +1,11 @@ +const fs = require('fs'); +const path = require('path'); + +describe('installing hybrid packages', () => { + it('should work', () => { + const content = fs.readFileSync( + path.join(process.env['TEST_SRCDIR'], 'npm', 'bazel_workspaces_consistent', 'a.txt'), + 'utf-8'); + expect(content).toEqual('some content'); + }); +}); diff --git a/package.json b/package.json index 5cc5a4e533..af73cc9376 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@types/semver": "6.2.0", "babel-jest": "^25.5.1", "bazel_workspaces": "file:./tools/npm_packages/bazel_workspaces", + "bazel_workspaces_consistent": "file:./tools/npm_packages/bazel_workspaces_consistent", "clang-format": "1.2.2", "conventional-changelog-cli": "^2.0.21", "core-util-is": "^1.0.2", diff --git a/tools/npm_packages/bazel_workspaces_consistent/BUILD.bazel b/tools/npm_packages/bazel_workspaces_consistent/BUILD.bazel new file mode 100644 index 0000000000..5764dd849e --- /dev/null +++ b/tools/npm_packages/bazel_workspaces_consistent/BUILD.bazel @@ -0,0 +1,17 @@ +load("@build_bazel_rules_nodejs//third_party/github.com/bazelbuild/bazel-skylib:rules/write_file.bzl", "write_file") +load(":index.bzl", "some_rule") + +# Just a dumb target to make sure we can use it from code that installs this npm package +write_file( + name = "some_file", + out = "a.txt", + content = ["some content"], + visibility = ["//visibility:public"], +) + +some_rule( + name = "test", + # Normally we would set the default to work in our source repo, + # and transform on publish. + text = "//tools/npm_packages/bazel_workspaces_consistent:a.txt", +) diff --git a/tools/npm_packages/bazel_workspaces_consistent/index.bzl b/tools/npm_packages/bazel_workspaces_consistent/index.bzl new file mode 100644 index 0000000000..47fc7ae071 --- /dev/null +++ b/tools/npm_packages/bazel_workspaces_consistent/index.bzl @@ -0,0 +1,13 @@ +"Simplest possible rule for testing it can be loaded and called" + +_ATTRS = { + # Note, we can reference our file without needing "@npm" which means it works + # regardless what name the user chooses for their workspace + "text": attr.label(default = Label("//bazel_workspaces_consistent:a.txt"), allow_single_file = True), +} + +def _impl(ctx): + # No actions, just echo the input file as the default output + return [DefaultInfo(files = depset(ctx.files.text))] + +some_rule = rule(_impl, attrs = _ATTRS) diff --git a/tools/npm_packages/bazel_workspaces_consistent/package.json b/tools/npm_packages/bazel_workspaces_consistent/package.json new file mode 100644 index 0000000000..f74fcc68eb --- /dev/null +++ b/tools/npm_packages/bazel_workspaces_consistent/package.json @@ -0,0 +1,6 @@ +{ + "name": "bazel_workspaces_consistent", + "version": "0.0.1", + "description": "https://hackmd.io/JkUESy8JTkyvGIlc-vNjeQ" + } + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 61ce916c4a..7a3d6c3def 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2220,6 +2220,9 @@ base@^0.11.1: "bazel_workspaces@file:./tools/npm_packages/bazel_workspaces": version "0.0.2" +"bazel_workspaces_consistent@file:./tools/npm_packages/bazel_workspaces_consistent": + version "0.0.1" + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"