Skip to content

Commit c9520bf

Browse files
authored
✨ - Support rewatch for incremental compilation (#965)
* ✨ - Support rewatch for incremental compilation * fix path * make it actually work :) * add basic cache entry * fix path * add changelog entry
1 parent 3aeb7e3 commit c9520bf

File tree

2 files changed

+158
-66
lines changed

2 files changed

+158
-66
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
- Make sure doc strings are always on top in hovers. https://github.com/rescript-lang/rescript-vscode/pull/956
1818

19+
#### :rocket: New Feature
20+
21+
- Add support for the rewatch build system for incremental compilation. https://github.com/rescript-lang/rescript-vscode/pull/965
22+
1923
## 1.50.0
2024

2125
#### :rocket: New Feature

server/src/incrementalCompilation.ts

+154-66
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ function debug() {
2020
const INCREMENTAL_FOLDER_NAME = "___incremental";
2121
const INCREMENTAL_FILE_FOLDER_LOCATION = `lib/bs/${INCREMENTAL_FOLDER_NAME}`;
2222

23+
type RewatchCompilerArgs = {
24+
compiler_args: Array<string>;
25+
parser_args: Array<string>;
26+
};
27+
2328
type IncrementallyCompiledFileInfo = {
2429
file: {
2530
/** File type. */
@@ -44,6 +49,11 @@ type IncrementallyCompiledFileInfo = {
4449
/** The raw, extracted needed info from build.ninja. Needs processing. */
4550
rawExtracted: Array<string>;
4651
} | null;
52+
/** Cache for rewatch compiler args. */
53+
buildRewatch: {
54+
lastFile: string;
55+
compilerArgs: RewatchCompilerArgs;
56+
} | null;
4757
/** Info of the currently active incremental compilation. `null` if no incremental compilation is active. */
4858
compilation: {
4959
/** The timeout of the currently active compilation for this incremental file. */
@@ -57,6 +67,8 @@ type IncrementallyCompiledFileInfo = {
5767
project: {
5868
/** The root path of the project. */
5969
rootPath: string;
70+
/** The root path of the workspace (if a monorepo) */
71+
workspaceRootPath: string;
6072
/** Computed location of bsc. */
6173
bscBinaryLocation: string;
6274
/** The arguments needed for bsc, derived from the project configuration/build.ninja. */
@@ -176,96 +188,139 @@ export function cleanUpIncrementalFiles(
176188
}
177189
function getBscArgs(
178190
entry: IncrementallyCompiledFileInfo
179-
): Promise<Array<string> | null> {
191+
): Promise<Array<string> | RewatchCompilerArgs | null> {
180192
const buildNinjaPath = path.resolve(
181193
entry.project.rootPath,
182194
"lib/bs/build.ninja"
183195
);
196+
const rewatchLockfile = path.resolve(
197+
entry.project.workspaceRootPath,
198+
"lib/rewatch.lock"
199+
);
200+
let buildSystem: "bsb" | "rewatch" | null = null;
201+
184202
let stat: fs.Stats | null = null;
185203
try {
186204
stat = fs.statSync(buildNinjaPath);
187-
} catch {
188-
if (debug()) {
189-
console.log("Did not find build.ninja, cannot proceed..");
190-
}
205+
buildSystem = "bsb";
206+
} catch {}
207+
try {
208+
stat = fs.statSync(rewatchLockfile);
209+
buildSystem = "rewatch";
210+
} catch {}
211+
if (buildSystem == null) {
212+
console.log("Did not find build.ninja or rewatch.lock, cannot proceed..");
191213
return Promise.resolve(null);
192214
}
193-
const cacheEntry = entry.buildNinja;
215+
const bsbCacheEntry = entry.buildNinja;
216+
const rewatchCacheEntry = entry.buildRewatch;
217+
194218
if (
195-
cacheEntry != null &&
219+
buildSystem === "bsb" &&
220+
bsbCacheEntry != null &&
196221
stat != null &&
197-
cacheEntry.fileMtime >= stat.mtimeMs
222+
bsbCacheEntry.fileMtime >= stat.mtimeMs
223+
) {
224+
return Promise.resolve(bsbCacheEntry.rawExtracted);
225+
}
226+
if (
227+
buildSystem === "rewatch" &&
228+
rewatchCacheEntry != null &&
229+
rewatchCacheEntry.lastFile === entry.file.sourceFilePath
198230
) {
199-
return Promise.resolve(cacheEntry.rawExtracted);
231+
return Promise.resolve(rewatchCacheEntry.compilerArgs);
200232
}
201233
return new Promise((resolve, _reject) => {
202-
function resolveResult(result: Array<string>) {
203-
if (stat != null) {
234+
function resolveResult(result: Array<string> | RewatchCompilerArgs) {
235+
if (stat != null && Array.isArray(result)) {
204236
entry.buildNinja = {
205237
fileMtime: stat.mtimeMs,
206238
rawExtracted: result,
207239
};
240+
} else if (!Array.isArray(result)) {
241+
entry.buildRewatch = {
242+
lastFile: entry.file.sourceFilePath,
243+
compilerArgs: result,
244+
};
208245
}
209246
resolve(result);
210247
}
211-
const fileStream = fs.createReadStream(buildNinjaPath);
212-
const rl = readline.createInterface({
213-
input: fileStream,
214-
crlfDelay: Infinity,
215-
});
216-
let captureNextLine = false;
217-
let done = false;
218-
let stopped = false;
219-
const captured: Array<string> = [];
220-
rl.on("line", (line) => {
221-
if (stopped) {
222-
return;
223-
}
224-
if (captureNextLine) {
225-
captured.push(line);
226-
captureNextLine = false;
227-
}
228-
if (done) {
229-
fileStream.destroy();
230-
rl.close();
248+
249+
if (buildSystem === "bsb") {
250+
const fileStream = fs.createReadStream(buildNinjaPath);
251+
const rl = readline.createInterface({
252+
input: fileStream,
253+
crlfDelay: Infinity,
254+
});
255+
let captureNextLine = false;
256+
let done = false;
257+
let stopped = false;
258+
const captured: Array<string> = [];
259+
rl.on("line", (line) => {
260+
if (stopped) {
261+
return;
262+
}
263+
if (captureNextLine) {
264+
captured.push(line);
265+
captureNextLine = false;
266+
}
267+
if (done) {
268+
fileStream.destroy();
269+
rl.close();
270+
resolveResult(captured);
271+
stopped = true;
272+
return;
273+
}
274+
if (line.startsWith("rule astj")) {
275+
captureNextLine = true;
276+
}
277+
if (line.startsWith("rule mij")) {
278+
captureNextLine = true;
279+
done = true;
280+
}
281+
});
282+
rl.on("close", () => {
231283
resolveResult(captured);
232-
stopped = true;
233-
return;
234-
}
235-
if (line.startsWith("rule astj")) {
236-
captureNextLine = true;
237-
}
238-
if (line.startsWith("rule mij")) {
239-
captureNextLine = true;
240-
done = true;
284+
});
285+
} else if (buildSystem === "rewatch") {
286+
try {
287+
let rewatchPath = path.resolve(
288+
entry.project.workspaceRootPath,
289+
"node_modules/@rolandpeelen/rewatch/rewatch"
290+
);
291+
const compilerArgs = JSON.parse(
292+
cp
293+
.execFileSync(rewatchPath, [
294+
"--rescript-version",
295+
entry.project.rescriptVersion,
296+
"--compiler-args",
297+
entry.file.sourceFilePath,
298+
])
299+
.toString()
300+
.trim()
301+
) as RewatchCompilerArgs;
302+
resolveResult(compilerArgs);
303+
} catch (e) {
304+
console.error(e);
241305
}
242-
});
243-
rl.on("close", () => {
244-
resolveResult(captured);
245-
});
306+
}
246307
});
247308
}
248-
function argsFromCommandString(cmdString: string): Array<Array<string>> {
249-
const s = cmdString
250-
.trim()
251-
.split("command = ")[1]
252-
.split(" ")
253-
.map((v) => v.trim())
254-
.filter((v) => v !== "");
255-
const args: Array<Array<string>> = [];
256309

257-
for (let i = 0; i <= s.length - 1; i++) {
258-
const item = s[i];
310+
function argCouples(argList: string[]): string[][] {
311+
let args: string[][] = [];
312+
for (let i = 0; i <= argList.length - 1; i++) {
313+
const item = argList[i];
259314
const nextIndex = i + 1;
260-
const nextItem = s[nextIndex] ?? "";
315+
const nextItem = argList[nextIndex] ?? "";
261316
if (item.startsWith("-") && nextItem.startsWith("-")) {
262317
// Single entry arg
263318
args.push([item]);
264319
} else if (item.startsWith("-") && nextItem.startsWith("'")) {
265320
// Quoted arg, take until ending '
266321
const arg = [nextItem.slice(1)];
267-
for (let x = nextIndex + 1; x <= s.length - 1; x++) {
268-
let subItem = s[x];
322+
for (let x = nextIndex + 1; x <= argList.length - 1; x++) {
323+
let subItem = argList[x];
269324
let break_ = false;
270325
if (subItem.endsWith("'")) {
271326
subItem = subItem.slice(0, subItem.length - 1);
@@ -284,6 +339,17 @@ function argsFromCommandString(cmdString: string): Array<Array<string>> {
284339
}
285340
return args;
286341
}
342+
343+
function argsFromCommandString(cmdString: string): Array<Array<string>> {
344+
const argList = cmdString
345+
.trim()
346+
.split("command = ")[1]
347+
.split(" ")
348+
.map((v) => v.trim())
349+
.filter((v) => v !== "");
350+
351+
return argCouples(argList);
352+
}
287353
function removeAnsiCodes(s: string): string {
288354
const ansiEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
289355
return s.replace(ansiEscape, "");
@@ -298,6 +364,9 @@ function triggerIncrementalCompilationOfFile(
298364
if (incrementalFileCacheEntry == null) {
299365
// New file
300366
const projectRootPath = utils.findProjectRootOfFile(filePath);
367+
const workspaceRootPath = projectRootPath
368+
? utils.findProjectRootOfFile(projectRootPath)
369+
: null;
301370
if (projectRootPath == null) {
302371
if (debug())
303372
console.log("Did not find project root path for " + filePath);
@@ -362,12 +431,14 @@ function triggerIncrementalCompilationOfFile(
362431
incrementalFilePath: path.join(incrementalFolderPath, moduleName + ext),
363432
},
364433
project: {
434+
workspaceRootPath: workspaceRootPath ?? projectRootPath,
365435
rootPath: projectRootPath,
366436
callArgs: Promise.resolve([]),
367437
bscBinaryLocation,
368438
incrementalFolderPath,
369439
rescriptVersion,
370440
},
441+
buildRewatch: null,
371442
buildNinja: null,
372443
compilation: null,
373444
killCompilationListeners: [],
@@ -419,11 +490,17 @@ function verifyTriggerToken(filePath: string, triggerToken: number): boolean {
419490
async function figureOutBscArgs(entry: IncrementallyCompiledFileInfo) {
420491
const res = await getBscArgs(entry);
421492
if (res == null) return null;
422-
const [astBuildCommand, fullBuildCommand] = res;
423-
424-
const astArgs = argsFromCommandString(astBuildCommand);
425-
const buildArgs = argsFromCommandString(fullBuildCommand);
426-
493+
let astArgs: Array<Array<string>> = [];
494+
let buildArgs: Array<Array<string>> = [];
495+
let isBsb = Array.isArray(res);
496+
if (Array.isArray(res)) {
497+
const [astBuildCommand, fullBuildCommand] = res;
498+
astArgs = argsFromCommandString(astBuildCommand);
499+
buildArgs = argsFromCommandString(fullBuildCommand);
500+
} else {
501+
astArgs = argCouples(res.parser_args);
502+
buildArgs = argCouples(res.compiler_args);
503+
}
427504
let callArgs: Array<string> = [];
428505

429506
if (config.extensionConfiguration.incrementalTypechecking?.acrossFiles) {
@@ -435,10 +512,21 @@ async function figureOutBscArgs(entry: IncrementallyCompiledFileInfo) {
435512

436513
buildArgs.forEach(([key, value]: Array<string>) => {
437514
if (key === "-I") {
438-
callArgs.push(
439-
"-I",
440-
path.resolve(entry.project.rootPath, "lib/bs", value)
441-
);
515+
if (isBsb) {
516+
callArgs.push(
517+
"-I",
518+
path.resolve(entry.project.rootPath, "lib/bs", value)
519+
);
520+
} else {
521+
if (value === ".") {
522+
callArgs.push(
523+
"-I",
524+
path.resolve(entry.project.rootPath, "lib/ocaml")
525+
);
526+
} else {
527+
callArgs.push("-I", value);
528+
}
529+
}
442530
} else if (key === "-bs-v") {
443531
callArgs.push("-bs-v", Date.now().toString());
444532
} else if (key === "-bs-package-output") {

0 commit comments

Comments
 (0)