Fix LavaMoat background policy generation (#12844)

The LavaMoat policy generation script would sporadically fail because
it ran the build concurrently three times, and the build includes
steps that delete the `dist` directory and write to it. So if one build
process tried to write to the directory after another deleted it, it
would fail.

This was solved by adding a new `--policy-only` flag to the build
script, and a new `scripts:prod` task. The `scripts:prod` task only
runs the script tasks for prod, rather than the entire build process.
The `--policy-only` flag stops the script tasks once the policy has
been written, and stops any other files from being written to disk.

This prevents the three concurrent build processes from getting in each
others way, and it dramatically speeds up the process.
feature/default_network_editable
Mark Stacey 3 years ago committed by Dan Miller
parent a6829f787c
commit 9fd42f1bc2
  1. 3
      development/build/README.md
  2. 11
      development/build/index.js
  3. 32
      development/build/scripts.js
  4. 4
      development/build/task.js
  5. 6
      development/generate-lavamoat-policies.sh

@ -52,6 +52,9 @@ Options:
bundle. Setting this to `false` is useful e.g. when bundle. Setting this to `false` is useful e.g. when
linking dependencies that are incompatible with lockdown. linking dependencies that are incompatible with lockdown.
[boolean] [default: true] [boolean] [default: true]
--policy-only Stops the build after generating the LavaMoat policy,
skipping any writes to disk.
[boolean] [deafult: false]
--skip-stats Whether to refrain from logging build progress. Mostly --skip-stats Whether to refrain from logging build progress. Mostly
used internally. used internally.
[boolean] [default: false] [boolean] [default: false]

@ -52,6 +52,7 @@ function defineAndRunBuildTasks() {
buildType, buildType,
entryTask, entryTask,
isLavaMoat, isLavaMoat,
policyOnly,
shouldIncludeLockdown, shouldIncludeLockdown,
shouldLintFenceFiles, shouldLintFenceFiles,
skipStats, skipStats,
@ -84,6 +85,7 @@ function defineAndRunBuildTasks() {
ignoredFiles, ignoredFiles,
isLavaMoat, isLavaMoat,
livereload, livereload,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
}); });
@ -134,6 +136,9 @@ function defineAndRunBuildTasks() {
), ),
); );
// build just production scripts, for LavaMoat policy generation purposes
createTask('scripts:prod', scriptTasks.prod);
// build for CI testing // build for CI testing
createTask( createTask(
'test', 'test',
@ -157,6 +162,7 @@ function parseArgv() {
BuildType: 'build-type', BuildType: 'build-type',
LintFenceFiles: 'lint-fence-files', LintFenceFiles: 'lint-fence-files',
Lockdown: 'lockdown', Lockdown: 'lockdown',
PolicyOnly: 'policy-only',
SkipStats: 'skip-stats', SkipStats: 'skip-stats',
}; };
@ -164,6 +170,7 @@ function parseArgv() {
boolean: [ boolean: [
NamedArgs.LintFenceFiles, NamedArgs.LintFenceFiles,
NamedArgs.Lockdown, NamedArgs.Lockdown,
NamedArgs.PolicyOnly,
NamedArgs.SkipStats, NamedArgs.SkipStats,
], ],
string: [NamedArgs.BuildType], string: [NamedArgs.BuildType],
@ -171,6 +178,7 @@ function parseArgv() {
[NamedArgs.BuildType]: BuildType.main, [NamedArgs.BuildType]: BuildType.main,
[NamedArgs.LintFenceFiles]: true, [NamedArgs.LintFenceFiles]: true,
[NamedArgs.Lockdown]: true, [NamedArgs.Lockdown]: true,
[NamedArgs.PolicyOnly]: false,
[NamedArgs.SkipStats]: false, [NamedArgs.SkipStats]: false,
}, },
}); });
@ -198,10 +206,13 @@ function parseArgv() {
? argv[NamedArgs.LintFenceFiles] ? argv[NamedArgs.LintFenceFiles]
: !/dev/iu.test(entryTask); : !/dev/iu.test(entryTask);
const policyOnly = argv[NamedArgs.PolicyOnly];
return { return {
buildType, buildType,
entryTask, entryTask,
isLavaMoat: process.argv[0].includes('lavamoat'), isLavaMoat: process.argv[0].includes('lavamoat'),
policyOnly,
shouldIncludeLockdown: argv[NamedArgs.Lockdown], shouldIncludeLockdown: argv[NamedArgs.Lockdown],
shouldLintFenceFiles, shouldLintFenceFiles,
skipStats: argv[NamedArgs.SkipStats], skipStats: argv[NamedArgs.SkipStats],

@ -134,6 +134,10 @@ function getSegmentWriteKey({ buildType, environment }) {
throw new Error(`Invalid build type: '${buildType}'`); throw new Error(`Invalid build type: '${buildType}'`);
} }
const noopWriteStream = through.obj((_file, _fileEncoding, callback) =>
callback(),
);
module.exports = createScriptTasks; module.exports = createScriptTasks;
function createScriptTasks({ function createScriptTasks({
@ -143,6 +147,7 @@ function createScriptTasks({
isLavaMoat, isLavaMoat,
livereload, livereload,
shouldLintFenceFiles, shouldLintFenceFiles,
policyOnly,
}) { }) {
// internal tasks // internal tasks
const core = { const core = {
@ -185,6 +190,7 @@ function createScriptTasks({
return `./app/scripts/${label}.js`; return `./app/scripts/${label}.js`;
}), }),
ignoredFiles, ignoredFiles,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
testing, testing,
}), }),
@ -241,6 +247,7 @@ function createScriptTasks({
runInChildProcess(subtask, { runInChildProcess(subtask, {
buildType, buildType,
isLavaMoat, isLavaMoat,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
}), }),
); );
@ -258,6 +265,8 @@ function createScriptTasks({
entryFilepath: `./app/scripts/${label}.js`, entryFilepath: `./app/scripts/${label}.js`,
ignoredFiles, ignoredFiles,
label, label,
testing,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
}); });
} }
@ -272,6 +281,8 @@ function createScriptTasks({
entryFilepath: `./app/scripts/${label}.js`, entryFilepath: `./app/scripts/${label}.js`,
ignoredFiles, ignoredFiles,
label, label,
testing,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
}); });
} }
@ -286,6 +297,8 @@ function createScriptTasks({
entryFilepath: `./app/scripts/${label}.js`, entryFilepath: `./app/scripts/${label}.js`,
ignoredFiles, ignoredFiles,
label, label,
testing,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
}); });
} }
@ -303,6 +316,7 @@ function createScriptTasks({
entryFilepath: `./app/scripts/${inpage}.js`, entryFilepath: `./app/scripts/${inpage}.js`,
label: inpage, label: inpage,
ignoredFiles, ignoredFiles,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
testing, testing,
}), }),
@ -314,6 +328,7 @@ function createScriptTasks({
entryFilepath: `./app/scripts/${contentscript}.js`, entryFilepath: `./app/scripts/${contentscript}.js`,
label: contentscript, label: contentscript,
ignoredFiles, ignoredFiles,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
testing, testing,
}), }),
@ -327,6 +342,7 @@ function createFactoredBuild({
devMode, devMode,
entryFiles, entryFiles,
ignoredFiles, ignoredFiles,
policyOnly,
shouldLintFenceFiles, shouldLintFenceFiles,
testing, testing,
}) { }) {
@ -346,6 +362,7 @@ function createFactoredBuild({
devMode, devMode,
envVars, envVars,
ignoredFiles, ignoredFiles,
policyOnly,
minify, minify,
reloadOnChange, reloadOnChange,
shouldLintFenceFiles, shouldLintFenceFiles,
@ -417,12 +434,17 @@ function createFactoredBuild({
// setup bundle destination // setup bundle destination
browserPlatforms.forEach((platform) => { browserPlatforms.forEach((platform) => {
const dest = `./dist/${platform}/`; const dest = `./dist/${platform}/`;
pipeline.get('dest').push(gulp.dest(dest)); const destination = policyOnly ? noopWriteStream : gulp.dest(dest);
pipeline.get('dest').push(destination);
}); });
}); });
// wait for bundle completion for postprocessing // wait for bundle completion for postprocessing
events.on('bundleDone', () => { events.on('bundleDone', () => {
// Skip HTML generation if nothing is to be written to disk
if (policyOnly) {
return;
}
const commonSet = sizeGroupMap.get('common'); const commonSet = sizeGroupMap.get('common');
// create entry points for each file // create entry points for each file
for (const [groupLabel, groupSet] of sizeGroupMap.entries()) { for (const [groupLabel, groupSet] of sizeGroupMap.entries()) {
@ -496,6 +518,7 @@ function createNormalBundle({
extraEntries = [], extraEntries = [],
ignoredFiles, ignoredFiles,
label, label,
policyOnly,
modulesToExpose, modulesToExpose,
shouldLintFenceFiles, shouldLintFenceFiles,
testing, testing,
@ -516,6 +539,7 @@ function createNormalBundle({
devMode, devMode,
envVars, envVars,
ignoredFiles, ignoredFiles,
policyOnly,
minify, minify,
reloadOnChange, reloadOnChange,
shouldLintFenceFiles, shouldLintFenceFiles,
@ -540,7 +564,8 @@ function createNormalBundle({
// setup bundle destination // setup bundle destination
browserPlatforms.forEach((platform) => { browserPlatforms.forEach((platform) => {
const dest = `./dist/${platform}/`; const dest = `./dist/${platform}/`;
pipeline.get('dest').push(gulp.dest(dest)); const destination = policyOnly ? noopWriteStream : gulp.dest(dest);
pipeline.get('dest').push(destination);
}); });
}); });
@ -570,6 +595,7 @@ function setupBundlerDefaults(
devMode, devMode,
envVars, envVars,
ignoredFiles, ignoredFiles,
policyOnly,
minify, minify,
reloadOnChange, reloadOnChange,
shouldLintFenceFiles, shouldLintFenceFiles,
@ -613,6 +639,7 @@ function setupBundlerDefaults(
setupReloadOnChange(buildConfiguration); setupReloadOnChange(buildConfiguration);
} }
if (!policyOnly) {
if (minify) { if (minify) {
setupMinification(buildConfiguration); setupMinification(buildConfiguration);
} }
@ -620,6 +647,7 @@ function setupBundlerDefaults(
// Setup source maps // Setup source maps
setupSourcemaps(buildConfiguration, { devMode }); setupSourcemaps(buildConfiguration, { devMode });
} }
}
function setupReloadOnChange({ bundlerOpts, events }) { function setupReloadOnChange({ bundlerOpts, events }) {
// Add plugin to options // Add plugin to options

@ -50,7 +50,7 @@ function createTask(taskName, taskFn) {
function runInChildProcess( function runInChildProcess(
task, task,
{ buildType, isLavaMoat, shouldLintFenceFiles }, { buildType, isLavaMoat, policyOnly, shouldLintFenceFiles },
) { ) {
const taskName = typeof task === 'string' ? task : task.taskName; const taskName = typeof task === 'string' ? task : task.taskName;
if (!taskName) { if (!taskName) {
@ -74,6 +74,7 @@ function runInChildProcess(
'--lint-fence-files', '--lint-fence-files',
shouldLintFenceFiles, shouldLintFenceFiles,
'--skip-stats', '--skip-stats',
...(policyOnly ? ['--policy-only'] : []),
], ],
{ {
env: process.env, env: process.env,
@ -90,6 +91,7 @@ function runInChildProcess(
'--lint-fence-files', '--lint-fence-files',
shouldLintFenceFiles, shouldLintFenceFiles,
'--skip-stats', '--skip-stats',
...(policyOnly ? ['--policy-only'] : []),
], ],
{ {
env: process.env, env: process.env,

@ -8,6 +8,6 @@ set -o pipefail
# type. # type.
# ATTN: This may tax your device when running it locally. # ATTN: This may tax your device when running it locally.
concurrently --kill-others-on-fail -n main,beta,flask \ concurrently --kill-others-on-fail -n main,beta,flask \
"WRITE_AUTO_POLICY=1 yarn dist" \ "WRITE_AUTO_POLICY=1 yarn build scripts:prod --policy-only" \
"WRITE_AUTO_POLICY=1 yarn dist --build-type beta" \ "WRITE_AUTO_POLICY=1 yarn build scripts:prod --policy-only --build-type beta" \
"WRITE_AUTO_POLICY=1 yarn dist --build-type flask" "WRITE_AUTO_POLICY=1 yarn build scripts:prod --policy-only --build-type flask"

Loading…
Cancel
Save