Skip to content

Commit

Permalink
feat: support bazel+js packages that install into regular @npm//packa…
Browse files Browse the repository at this point in the history
…ge:index.bzl location
  • Loading branch information
alexeagle committed May 4, 2020
1 parent 2e93d96 commit 4f508b1
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 18 deletions.
57 changes: 48 additions & 9 deletions internal/npm_install/generate_build_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
50 changes: 41 additions & 9 deletions internal/npm_install/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down
10 changes: 10 additions & 0 deletions internal/npm_install/test/npm_packages/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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"],
)
5 changes: 5 additions & 0 deletions internal/npm_install/test/npm_packages/README.md
Original file line number Diff line number Diff line change
@@ -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*
11 changes: 11 additions & 0 deletions internal/npm_install/test/npm_packages/spec.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
17 changes: 17 additions & 0 deletions tools/npm_packages/bazel_workspaces_consistent/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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",
)
13 changes: 13 additions & 0 deletions tools/npm_packages/bazel_workspaces_consistent/index.bzl
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 6 additions & 0 deletions tools/npm_packages/bazel_workspaces_consistent/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "bazel_workspaces_consistent",
"version": "0.0.1",
"description": "https://hackmd.io/JkUESy8JTkyvGIlc-vNjeQ"
}

3 changes: 3 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 4f508b1

Please sign in to comment.