Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/stamping.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ Bazel provides a couple of statuses by default, such as `BUILD_EMBED_LABEL` whic
argument, as well as `BUILD_HOST` and `BUILD_USER`. You can supply more with the workspace status script, see below.

Some rules accept an attribute that uses the status variables.
For example, in a `pkg_npm` you can use the `substitutions` attribute like:

## Substitutions attribute

In a `pkg_npm` or `pkg_web` you can use the `substitutions` attribute like:

```python
pkg_npm(
Expand All @@ -32,7 +35,10 @@ pkg_npm(
In a `--stamp` build, this will replace the string "0.0.0-PLACEHOLDER" in any file included in the package with the current value of the `STABLE_GIT_COMMIT` variable.
However without stamping the placeholder will be left as-is.

## Read the status files

The `rollup_bundle` rule just exposes the Bazel status files so that your custom `rollup.config.js` can read it.
See [Rollup](Rollup).

## Stamping with a Workspace status script

Expand Down
2 changes: 1 addition & 1 deletion internal/node/context.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ load("@build_bazel_rules_nodejs//:providers.bzl", "NodeContextInfo")

_DOC = """node_context_data gathers information about the build configuration.
It is a common dependency of all targets that are sensitive to configuration.
(currently pkg_npm and rollup_bundle)"""
(currently pkg_npm, pkg_web, and rollup_bundle)"""

def _impl(ctx):
return [NodeContextInfo(stamp = ctx.attr.stamp)]
Expand Down
6 changes: 5 additions & 1 deletion internal/pkg_web/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ filegroup(

nodejs_binary(
name = "assembler",
data = ["assembler.js"],
data = [
"assembler.js",
"//third_party/github.com/gjtorikian/isBinaryFile",
],
entry_point = ":assembler.js",
node_modules = ":node_modules_none",
)
Expand All @@ -32,6 +35,7 @@ jasmine_node_test(
srcs = ["assembler_spec.js"],
deps = [
"assembler.js",
"//third_party/github.com/gjtorikian/isBinaryFile",
],
)

Expand Down
70 changes: 66 additions & 4 deletions internal/pkg_web/assembler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

const fs = require('fs');
const path = require('path');
const isBinary = require('isbinaryfile').isBinaryFileSync;

/**
* Create a new directory and any necessary subdirectories
Expand All @@ -29,9 +30,58 @@ function mkdirp(p) {
}
}

function unquoteArgs(s) {
return s.replace(/^'(.*)'$/, '$1');
}

function getBazelStatusMappings(statusFilePath) {
if (!statusFilePath) return {};
const stampFileLines = fs.readFileSync(statusFilePath, {encoding: 'utf-8'}).trim().split('\n');
const stampMap = {};
for (const line of stampFileLines) {
const [key, value] = line.split(' ');
stampMap[key] = value;
}
return stampMap;
}

function normalizeSubstitutions(substitutionsArg, stampMap) {
const substitutions = JSON.parse(substitutionsArg);

const normalizedSubstitutions = {};

for (const occurrence in substitutions) {
let substituteWith = substitutions[occurrence];
if (substituteWith.match(/^{.*?}$/)) {
substituteWith = substituteWith.replace(/^{(.*?)}$/, '$1');
if (!stampMap[substituteWith]) {
throw new Error(`Could not find ${substituteWith} key in status file.`);
}
substituteWith = stampMap[substituteWith];
}
normalizedSubstitutions[occurrence] = substituteWith;
}
return normalizedSubstitutions;
}

function main(params) {
const outdir = params.shift();

const volatileFilePath = params.shift();

const stableFilePath = params.shift();

const rawSubstitutions = params.shift().replace(/^'(.*)'$/, '$1');

const stampMap = {
...getBazelStatusMappings(volatileFilePath),
...getBazelStatusMappings(stableFilePath),
};

const normalizedSubstitutions = normalizeSubstitutions(rawSubstitutions, stampMap)

const substitutions = Object.entries(normalizedSubstitutions);

const rootDirs = [];
while (params.length && params[0] !== '--assets') {
let r = params.shift();
Expand All @@ -42,6 +92,7 @@ function main(params) {
}
// Always trim the longest prefix
rootDirs.sort((a, b) => b.length - a.length);

params.shift(); // --assets

function relative(execPath) {
Expand All @@ -56,12 +107,20 @@ function main(params) {
return execPath;
}

function copy(f) {
function copy(f, substitutions) {
if (fs.statSync(f).isDirectory()) {
for (const file of fs.readdirSync(f)) {
// Change paths to posix
copy(path.join(f, file).replace(/\\/g, '/'));
copy(path.join(f, file).replace(/\\/g, '/'), substitutions);
}
} else if (!isBinary(f)) {
const dest = path.join(outdir, relative(f));
let content = fs.readFileSync(f, {encoding: 'utf-8'});
substitutions.forEach(([occurrence, replaceWith]) => {
content = content.replace(occurrence, replaceWith);
});
fs.mkdirSync(path.dirname(dest), {recursive: true});
fs.writeFileSync(dest, content);
} else {
const dest = path.join(outdir, relative(f));
mkdirp(path.dirname(dest));
Expand All @@ -75,7 +134,7 @@ function main(params) {
// copied from within bazel-bin.
// See https://github.com/bazelbuild/rules_nodejs/pull/546.
for (const f of new Set(params)) {
copy(f);
copy(f, substitutions);
}
return 0;
}
Expand All @@ -85,6 +144,9 @@ module.exports = {main};
if (require.main === module) {
// We always require the arguments are encoded into a flagfile
// so that we don't exhaust the command-line limit.
const params = fs.readFileSync(process.argv[2], {encoding: 'utf-8'}).split('\n').filter(l => !!l);
const params = fs.readFileSync(process.argv[2], {encoding: 'utf-8'})
.split('\n')
.filter(l => !!l)
.map(unquoteArgs);
process.exitCode = main(params);
}
74 changes: 48 additions & 26 deletions internal/pkg_web/assembler_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,54 @@ const path = require('path');
const fs = require('fs');

describe('assembler', () => {
const outdir = 'output';
let testCount = 0;
beforeEach(() => {
const now = Date.now() + String(testCount++);
// prevent test isolation failures by running each spec in a separate dir
const uniqueDir = path.join(process.env['TEST_TMPDIR'], String(now));
fs.mkdirSync(uniqueDir);
process.chdir(uniqueDir);
fs.mkdirSync('path');
fs.mkdirSync('path/to');
fs.writeFileSync('path/to/thing1.txt', 'some content', {encoding: 'utf-8'});
});
const outdir = 'output';
const volatilePath = 'path/to/volatile-status.txt';
const stablePath = 'path/to/stable-status.txt';
let testCount = 0;
beforeEach(() => {
const now = Date.now() + String(testCount++);
// prevent test isolation failures by running each spec in a separate dir
const uniqueDir = path.join(process.env['TEST_TMPDIR'], String(now));
fs.mkdirSync(uniqueDir);
process.chdir(uniqueDir);
fs.mkdirSync('path');
fs.mkdirSync('path/to');
fs.writeFileSync('path/to/thing1.txt', 'some content', {encoding: 'utf-8'});
fs.writeFileSync(volatilePath, 'TEST_KEY 41561')
fs.writeFileSync(stablePath, 'STABLE_GIT_VERSION 123abc')
});

it('should copy files', () => {
assembler.main([outdir, '--assets', 'path/to/thing1.txt']);
expect(fs.readdirSync('output/path/to')).toContain('thing1.txt');
expect(fs.readFileSync('output/path/to/thing1.txt', {encoding: 'utf-8'})).toBe('some content');
});
it('should copy files', () => {
assembler.main([outdir, '', '', '{}', '--assets', 'path/to/thing1.txt']);
expect(fs.readdirSync('output/path/to')).toContain('thing1.txt');
expect(fs.readFileSync('output/path/to/thing1.txt', {encoding: 'utf-8'})).toBe('some content');
});

it('should strip longest rootdir', () => {
assembler.main([outdir, 'path', 'path/to', '--assets', 'path/to/thing1.txt']);
expect(fs.readdirSync('output')).toContain('thing1.txt');
});
it('should strip longest rootdir', () => {
assembler.main([outdir, '', '', '{}', 'path', 'path/to', '--assets', 'path/to/thing1.txt']);
expect(fs.readdirSync('output')).toContain('thing1.txt');
});

it('should handle nested directories', () => {
assembler.main([outdir, 'path', '--assets', 'path/to']);
expect(fs.readdirSync('output/to')).toContain('thing1.txt');
});
});
it('should handle nested directories', () => {
assembler.main([outdir, '', '', '{}', 'path', '--assets', 'path/to']);
expect(fs.readdirSync('output/to')).toContain('thing1.txt');
});

it('should replace contents with static text', () => {
assembler.main([
outdir, '', '', '{"some content":"some other content"}', '--assets', 'path/to/thing1.txt'
]);
expect(fs.readFileSync('output/path/to/thing1.txt', {
encoding: 'utf-8'
})).toBe('some other content');
})


it('should replace contents with dynamic text from Bazel workspace status', () => {
assembler.main([
outdir, volatilePath, stablePath, '{"some":"{STABLE_GIT_VERSION}", "content":"{TEST_KEY}"}',
'--assets', 'path/to/thing1.txt'
]);
expect(fs.readFileSync('output/path/to/thing1.txt', {encoding: 'utf-8'})).toBe('123abc 41561');
})
});
37 changes: 32 additions & 5 deletions internal/pkg_web/pkg_web.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,44 @@
"""Contains the pkg_web rule.
"""

load("//:providers.bzl", "NODE_CONTEXT_ATTRS", "NodeContextInfo")

_DOC = """Assembles a web application from source files."""

_ATTRS = {
_ATTRS = dict(NODE_CONTEXT_ATTRS, **{
"additional_root_paths": attr.string_list(
doc = """Path prefixes to strip off all srcs, in addition to the current package. Longest wins.""",
),
"srcs": attr.label_list(
allow_files = True,
doc = """Files which should be copied into the package""",
),
"substitutions": attr.string_dict(
doc = """Key-value pairs which are replaced in all the files while building the package.

You can use values from the workspace status command using curly braces, for example
`{"0.0.0-PLACEHOLDER": "{STABLE_GIT_VERSION}"}`.
See the section on stamping in the README.""",
),
"_assembler": attr.label(
default = "@build_bazel_rules_nodejs//internal/pkg_web:assembler",
executable = True,
cfg = "host",
),
}
})

def move_files(output_name, files, action_factory, var, assembler, root_paths):
def move_files(output_name, substitutions, version_file, info_file, stamp, files, action_factory, var, assembler, root_paths):
"""Moves files into an output directory

Args:
output_name: The name of the output directory
substitutions: key/value pairs to replace
version_file: bazel-out/volatile-status.txt
info_file: bazel-out/stable-status.txt
stamp: whether the build is performed with --stamp
files: The files to move
action_factory: Bazel's actions module from ctx.actions - see https://docs.bazel.build/versions/master/skylark/lib/actions.html
var: environment variables
assembler: The assembler executable
root_paths: Path prefixes to strip off all srcs. Longest wins.

Expand All @@ -47,13 +61,23 @@ def move_files(output_name, files, action_factory, var, assembler, root_paths):
"""
www_dir = action_factory.declare_directory(output_name)
args = action_factory.args()
inputs = files[:]
args.add(www_dir.path)
if stamp:
args.add(version_file.path)
inputs.append(version_file)
args.add(info_file.path)
inputs.append(info_file)
else:
args.add_all(["", ""])
args.add(substitutions)
args.add_all(root_paths)
args.add("--assets")
args.add_all([f.path for f in files])
args.use_param_file("%s", use_always = True)

action_factory.run(
inputs = files,
inputs = inputs,
outputs = [www_dir],
executable = assembler,
arguments = [args],
Expand Down Expand Up @@ -86,9 +110,12 @@ def additional_root_paths(ctx):

def _impl(ctx):
root_paths = additional_root_paths(ctx)

package_layout = move_files(
ctx.label.name,
ctx.attr.substitutions,
ctx.version_file,
ctx.info_file,
ctx.attr.node_context_data[NodeContextInfo].stamp,
ctx.files.srcs,
ctx.actions,
ctx.var,
Expand Down
18 changes: 11 additions & 7 deletions internal/pkg_web/test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_web")
load("@npm//@babel/cli:index.bzl", "babel")
load("//internal/node:context.bzl", "node_context_data")
load("//packages/jasmine:index.bzl", "jasmine_node_test")
load("//packages/rollup:index.bzl", "rollup_bundle")
load("//packages/terser:index.bzl", "terser_minified")
Expand Down Expand Up @@ -62,6 +63,11 @@ terser_minified(
debug = True,
)

node_context_data(
name = "force_stamp",
stamp = True,
)

pkg_web(
name = "pkg",
srcs = [
Expand All @@ -73,16 +79,14 @@ pkg_web(
":bundle.min_debug",
":bundle.min_debug.es2015",
],
node_context_data = ":force_stamp",
substitutions = {
"${version}": "{BUILD_SCM_VERSION}",
},
)

jasmine_node_test(
name = "test",
srcs = ["spec.js"],
data = [
"index_golden.html_",
":pkg",
],
tags = [
"fix-windows",
],
data = [":pkg"],
)
1 change: 0 additions & 1 deletion internal/pkg_web/test/index_golden.html_

This file was deleted.

3 changes: 2 additions & 1 deletion internal/pkg_web/test/script.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const el = document.createElement('div');
const text: string = 'Hello, World';
const version = '0.0.0-PLACEHOLDER';
const text: string = `Hello, World v${version}`;
el.innerText = text;
document.body.appendChild(el);
Loading