Alter build process to rely on canaries only
With the react-sdk and js-sdk having their `npm start`s split out (as per https://github.com/matrix-org/matrix-react-sdk/pull/2175 and https://github.com/matrix-org/matrix-js-sdk/pull/742) we can trigger an initial build ourselves and start the watcher afterwards. This canary approach has a very slight speed increase over serially running all the commands as the watcher can be started as early as possible. This all can be improved and potentially eliminated with a bit more planning, however: https://github.com/vector-im/riot-web/issues/7386
This commit is contained in:
parent
8d7cec2a94
commit
27c23058dc
4 changed files with 73 additions and 84 deletions
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -384,12 +384,6 @@
|
||||||
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
|
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"async-lock": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-nxlfFLGfCJ1r7p9zhR5OuL6jYkDd9P7FqSitfLji+C1NdyhCz4+rWW3kiPiyPASHhN7VlsKEvRWWbnME9lYngw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
@ -4180,12 +4174,14 @@
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
@ -4200,17 +4196,20 @@
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -4327,7 +4326,8 @@
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
@ -4339,6 +4339,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -4353,6 +4354,7 @@
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
@ -4360,7 +4362,8 @@
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
|
@ -4384,6 +4387,7 @@
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
|
@ -4464,7 +4468,8 @@
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -4597,6 +4602,7 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
|
|
@ -81,7 +81,6 @@
|
||||||
"url": "^0.11.0"
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async-lock": "^1.1.3",
|
|
||||||
"autoprefixer": "^6.6.0",
|
"autoprefixer": "^6.6.0",
|
||||||
"babel-cli": "^6.5.2",
|
"babel-cli": "^6.5.2",
|
||||||
"babel-core": "^6.14.0",
|
"babel-core": "^6.14.0",
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
const AsyncLock = require('async-lock');
|
|
||||||
|
|
||||||
|
|
||||||
// This script sits and waits for a build of an underlying SDK (js or react)
|
// This script waits for a signal that an underlying SDK (js or react) has finished
|
||||||
// to complete before exiting. This is done by cooperating with build-watch-sdk.js
|
// enough of the build process to be safe to rely on. In riot-web's case, this means
|
||||||
// by waiting for it's signal to start watching for file changes, then watching
|
// that the underlying SDK has finished an initial build and is getting ready to watch
|
||||||
// the SDK's build output for a storm of file changes to stop. Both the js-sdk
|
// for changes. This is done through use of a canary file that is deleted when it is
|
||||||
// and react-sdk compile each file one by one, so by waiting for file changes to
|
// safe to continue (see build-watch-sdk.js for why we listen for a delete event).
|
||||||
// stop we know it is safe to continue and therefore exit this script. We give
|
|
||||||
// some leeway to the SDK's build process to handle larger/more complex files
|
|
||||||
// through use of a reset-on-touch countdown timer. When a file change occurs,
|
|
||||||
// we reset the countdown to WAIT_TIME and let it count down. If the count down
|
|
||||||
// completes, we consider ourselves having left the file system update storm and
|
|
||||||
// therefore can consider a build of the SDK to be fully completed.
|
|
||||||
|
|
||||||
// Why do we block in the first place? Because if riot-web starts it's initial
|
// Why do we block in the first place? Because if riot-web starts it's initial
|
||||||
// build (via webpack-dev-server) and the react-sdk or js-sdk are halfway through
|
// build (via webpack-dev-server) and the react-sdk or js-sdk are halfway through
|
||||||
|
@ -26,21 +19,12 @@ const AsyncLock = require('async-lock');
|
||||||
// build which riot-web is waiting for. When complete, riot-web starts building as
|
// build which riot-web is waiting for. When complete, riot-web starts building as
|
||||||
// per normal.
|
// per normal.
|
||||||
|
|
||||||
// Why the canary to begin watching? Because we can't reliably determine that the
|
|
||||||
// build triggered by `npm install` in each SDK is actually the process we need to
|
|
||||||
// be watching for. To work around this, build-watch-sdk.js does the `npm install`
|
|
||||||
// and follows through with a canary to signal to this script that it should start
|
|
||||||
// watching for changes produced by that SDK's `npm start` (run immediately after
|
|
||||||
// the canary is sent).
|
|
||||||
|
|
||||||
|
|
||||||
const WAIT_TIME = 5000; // ms
|
|
||||||
|
|
||||||
function waitForCanary(canaryName) {
|
function waitForCanary(canaryName) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const filename = path.resolve(path.join(".tmp", canaryName));
|
const filename = path.resolve(path.join(".tmp", canaryName + ".canary"));
|
||||||
|
|
||||||
// See triggerCanarySignal in build-watch-sdk.js for why we watch for `unlink`
|
// See build-watch-sdk.js for why we listen for 'unlink' specifically.
|
||||||
const watcher = chokidar.watch(filename).on('unlink', (path) => {
|
const watcher = chokidar.watch(filename).on('unlink', (path) => {
|
||||||
console.log("[block-on-build] Received signal to start watching for builds");
|
console.log("[block-on-build] Received signal to start watching for builds");
|
||||||
watcher.close();
|
watcher.close();
|
||||||
|
@ -49,31 +33,6 @@ function waitForCanary(canaryName) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitOnSdkBuild(sdkName) {
|
|
||||||
// First we wait for a local canary file to be changed
|
|
||||||
return waitForCanary(sdkName).then(() => new Promise((resolve, reject) => {
|
|
||||||
const buildDirectory = path.dirname(require.resolve(`matrix-${sdkName}-sdk`));
|
|
||||||
const lock = new AsyncLock();
|
|
||||||
let timerId = null;
|
|
||||||
|
|
||||||
const watcher = chokidar.watch(buildDirectory).on('all', (event, path) => {
|
|
||||||
lock.acquire("timer", (done) => {
|
|
||||||
if (timerId !== null) {
|
|
||||||
//console.log("Resetting countdown");
|
|
||||||
clearTimeout(timerId);
|
|
||||||
}
|
|
||||||
//console.log(`Waiting ${WAIT_TIME}ms for another file update...`);
|
|
||||||
timerId = setTimeout(() => {
|
|
||||||
console.log("[block-on-build] No updates - unblocking");
|
|
||||||
watcher.close();
|
|
||||||
resolve();
|
|
||||||
}, WAIT_TIME);
|
|
||||||
done();
|
|
||||||
}, null, null);
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const sdkName = process.argv[2];
|
const sdkName = process.argv[2];
|
||||||
if (!sdkName) {
|
if (!sdkName) {
|
||||||
console.error("[block-on-build] No SDK name provided");
|
console.error("[block-on-build] No SDK name provided");
|
||||||
|
@ -81,7 +40,7 @@ if (!sdkName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[block-on-build] Waiting for SDK: " + sdkName);
|
console.log("[block-on-build] Waiting for SDK: " + sdkName);
|
||||||
waitOnSdkBuild(sdkName).then(() => {
|
waitForCanary(sdkName).then(() => {
|
||||||
console.log("[block-on-build] Unblocked");
|
console.log("[block-on-build] Unblocked");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
|
@ -16,7 +16,15 @@ if (!sdkName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const sdkPath = path.dirname(require.resolve(`matrix-${sdkName}-sdk/package.json`));
|
const sdkPath = path.dirname(require.resolve(`matrix-${sdkName}-sdk/package.json`));
|
||||||
console.log(sdkPath);
|
|
||||||
|
// Note: we intentionally create then delete the canary file to work
|
||||||
|
// around a file watching problem where if the file exists on startup it
|
||||||
|
// may fire a "created" event for the file. By having the behaviour be "do
|
||||||
|
// something on delete" we avoid accidentally firing the signal too early.
|
||||||
|
// We also need to ensure the create and delete events are not too close
|
||||||
|
// together, otherwise the filesystem may not fire the watcher. Therefore
|
||||||
|
// we create the canary as early as possible and delete it as late as possible.
|
||||||
|
prepareCanarySignal(sdkName);
|
||||||
|
|
||||||
// We only want to build the SDK if it looks like it was `npm link`ed
|
// We only want to build the SDK if it looks like it was `npm link`ed
|
||||||
if (fs.existsSync(path.join(sdkPath, '.git'))) {
|
if (fs.existsSync(path.join(sdkPath, '.git'))) {
|
||||||
|
@ -37,25 +45,37 @@ if (fs.existsSync(path.join(sdkPath, '.git'))) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a signal so that the various blocks can unblock. See the top of
|
// Prepare an initial build of the SDK
|
||||||
// block-on-sdk-build.js for more information on how this is used.
|
child_process.execSync("npm run start:init", {
|
||||||
console.log("Sending signal that other processes may unblock");
|
|
||||||
triggerCanarySignal(sdkName);
|
|
||||||
|
|
||||||
// Actually start the watcher process for the sdk. This is what block-on-sdk-build.js
|
|
||||||
// is going to monitor.
|
|
||||||
console.log("Performing task: " + task);
|
|
||||||
child_process.execSync(`npm ${task === "build" ? "run build" : "start"}`, {
|
|
||||||
env: process.env,
|
env: process.env,
|
||||||
cwd: sdkPath,
|
cwd: sdkPath,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
// Send a signal to unblock the build for other processes. Used by block-on-sdk-build.js
|
||||||
|
console.log("Sending signal that other processes may unblock");
|
||||||
|
triggerCanarySignal(sdkName);
|
||||||
|
|
||||||
|
// Actually start the watcher process for the SDK (without an initial build)
|
||||||
|
console.log("Performing task: " + task);
|
||||||
|
const watchTask = sdkName === 'js' ? "start:watch" : "start:all";
|
||||||
|
const buildTask = "build";
|
||||||
|
child_process.execSync(`npm run ${task === "build" ? buildTask : watchTask}`, {
|
||||||
|
env: process.env,
|
||||||
|
cwd: sdkPath,
|
||||||
|
});
|
||||||
|
} else triggerCanarySignal(sdkName);
|
||||||
|
|
||||||
function triggerCanarySignal(sdkName) {
|
function triggerCanarySignal(sdkName) {
|
||||||
const tmpPath = path.resolve(".tmp");
|
fs.unlinkSync(getCanaryPath(sdkName));
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareCanarySignal(sdkName) {
|
||||||
|
const canaryPath = getCanaryPath(sdkName);
|
||||||
|
const canaryDir = path.dirname(canaryPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(tmpPath);
|
console.log("Creating canary temp path...");
|
||||||
|
fs.mkdirSync(canaryDir);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code !== 'EEXIST') {
|
if (e.code !== 'EEXIST') {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -63,12 +83,17 @@ function triggerCanarySignal(sdkName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we intentionally create then delete the file to work around
|
try {
|
||||||
// a file watching problem where if the file exists on startup it may
|
console.log("Creating canary file: " + canaryPath);
|
||||||
// fire a "created" event for the file. By having the behaviour be "do
|
fs.closeSync(fs.openSync(canaryPath, 'w'));
|
||||||
// something on delete" we avoid accidentally firing the signal too
|
} catch (e) {
|
||||||
// early.
|
if (e.code !== 'EEXIST') {
|
||||||
const canaryPath = path.join(tmpPath, sdkName);
|
console.error(e);
|
||||||
fs.closeSync(fs.openSync(canaryPath, 'w'));
|
throw "Failed to create canary file";
|
||||||
fs.unlinkSync(canaryPath);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCanaryPath(sdkName) {
|
||||||
|
return path.join(path.resolve(".tmp"), sdkName + ".canary");
|
||||||
}
|
}
|
Loading…
Reference in a new issue