Summary:
On Windows VM when "yarn start" is executed and compilation is in progress for some plugin, fs.watch randomly fires "changed" events for different files of other plugins. This leads to infinite attempts to rebuild the same plugin again and again, and this process never ends, so "yarn start" is almost unusable:
{F225467225}
I've tried to fix this by using watchman instead of fs.watch and on my tests with Windows build it works well:
{F225467508}
Also as watchman is more careful about opening file handles, hopefully this change will fix "too many files opened" problem as Michel suggested here https://fb.workplace.com/groups/flippersupport/permalink/764157990731528/ and here https://github.com/facebook/flipper/issues/699.
Reviewed By: mweststrate
Differential Revision: D19216026
fbshipit-source-id: acc53ae0d003a7936730e6423ac4dbca84f089c8
104 lines
2.7 KiB
JavaScript
104 lines
2.7 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
const watchman = require('fb-watchman');
|
|
const uuid = require('uuid');
|
|
const path = require('path');
|
|
|
|
module.exports = class Watchman {
|
|
constructor(rootDir) {
|
|
this.rootDir = rootDir;
|
|
}
|
|
|
|
async initialize() {
|
|
if (this.client) {
|
|
return;
|
|
}
|
|
this.client = new watchman.Client();
|
|
this.client.setMaxListeners(250);
|
|
return new Promise((resolve, reject) => {
|
|
this.client.capabilityCheck(
|
|
{optional: [], required: ['relative_root']},
|
|
error => {
|
|
if (error) {
|
|
this.client.end();
|
|
delete this.client;
|
|
return reject(error);
|
|
}
|
|
this.client.command(
|
|
['watch-project', this.rootDir],
|
|
(error, resp) => {
|
|
if (error) {
|
|
this.client.end();
|
|
delete this.client;
|
|
return reject(error);
|
|
}
|
|
if ('warning' in resp) {
|
|
console.warn(resp.warning);
|
|
}
|
|
this.watch = resp.watch;
|
|
this.relativeRoot = resp.relative_path;
|
|
resolve();
|
|
},
|
|
);
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
async startWatchFiles(relativeDir, handler, options) {
|
|
if (!this.watch) {
|
|
throw new Error(
|
|
'Watchman is not initialized, please call "initialize" function and wait for the returned promise completion before calling "startWatchFiles".',
|
|
);
|
|
}
|
|
options = Object.assign({excludes: []}, options);
|
|
return new Promise((resolve, reject) => {
|
|
this.client.command(['clock', this.watch], (error, resp) => {
|
|
if (error) {
|
|
return reject(error);
|
|
}
|
|
|
|
const {clock} = resp;
|
|
|
|
const sub = {
|
|
expression: [
|
|
'allof',
|
|
['not', ['type', 'd']],
|
|
...options.excludes.map(e => ['not', ['match', e, 'wholename']]),
|
|
],
|
|
fields: ['name'],
|
|
since: clock,
|
|
relative_root: this.relativeRoot
|
|
? path.join(this.relativeRoot, relativeDir)
|
|
: relativeDir,
|
|
};
|
|
|
|
const id = uuid.v4();
|
|
|
|
this.client.command(
|
|
['subscribe', this.watch, id, sub],
|
|
(error, resp) => {
|
|
if (error) {
|
|
return reject(error);
|
|
}
|
|
this.client.on('subscription', resp => {
|
|
if (resp.subscription !== id || !resp.files) {
|
|
return;
|
|
}
|
|
handler(resp);
|
|
});
|
|
resolve();
|
|
},
|
|
);
|
|
});
|
|
});
|
|
}
|
|
};
|