Skip to content

✨ - Support rewatch for incremental compilation #965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 12, 2024
Merged
Changes from 2 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
170 changes: 112 additions & 58 deletions server/src/incrementalCompilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ function debug() {
const INCREMENTAL_FOLDER_NAME = "___incremental";
const INCREMENTAL_FILE_FOLDER_LOCATION = `lib/bs/${INCREMENTAL_FOLDER_NAME}`;

type RewatchCompilerArgs = {
compiler_args: Array<string>;
parser_args: Array<string>;
};

type IncrementallyCompiledFileInfo = {
file: {
/** File type. */
Expand All @@ -44,6 +49,10 @@ type IncrementallyCompiledFileInfo = {
/** The raw, extracted needed info from build.ninja. Needs processing. */
rawExtracted: Array<string>;
} | null;
/** Cache for rewatch compiler args. */
buildRewatch: {
compilerArgs: RewatchCompilerArgs;
} | null;
/** Info of the currently active incremental compilation. `null` if no incremental compilation is active. */
compilation: {
/** The timeout of the currently active compilation for this incremental file. */
Expand Down Expand Up @@ -176,18 +185,28 @@ export function cleanUpIncrementalFiles(
}
function getBscArgs(
entry: IncrementallyCompiledFileInfo
): Promise<Array<string> | null> {
): Promise<Array<string> | RewatchCompilerArgs | null> {
const buildNinjaPath = path.resolve(
entry.project.rootPath,
"lib/bs/build.ninja"
);
const rewatchLockfile = path.resolve(
entry.project.rootPath,
"lib/rewatch.lock"
);
let buildSystem: "bsb" | "rewatch" | null = null;

let stat: fs.Stats | null = null;
try {
stat = fs.statSync(buildNinjaPath);
} catch {
if (debug()) {
console.log("Did not find build.ninja, cannot proceed..");
}
buildSystem = "bsb";
} catch {}
try {
stat = fs.statSync(rewatchLockfile);
buildSystem = "rewatch";
} catch {}
if (buildSystem == null) {
console.log("Did not find build.ninja or rewatch.lock, cannot proceed..");
return Promise.resolve(null);
}
const cacheEntry = entry.buildNinja;
Expand All @@ -199,73 +218,91 @@ function getBscArgs(
return Promise.resolve(cacheEntry.rawExtracted);
}
return new Promise((resolve, _reject) => {
function resolveResult(result: Array<string>) {
if (stat != null) {
function resolveResult(result: Array<string> | RewatchCompilerArgs) {
if (stat != null && Array.isArray(result)) {
entry.buildNinja = {
fileMtime: stat.mtimeMs,
rawExtracted: result,
};
}
resolve(result);
}
const fileStream = fs.createReadStream(buildNinjaPath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
let captureNextLine = false;
let done = false;
let stopped = false;
const captured: Array<string> = [];
rl.on("line", (line) => {
if (stopped) {
return;
}
if (captureNextLine) {
captured.push(line);
captureNextLine = false;
}
if (done) {
fileStream.destroy();
rl.close();

if (buildSystem === "bsb") {
const fileStream = fs.createReadStream(buildNinjaPath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
let captureNextLine = false;
let done = false;
let stopped = false;
const captured: Array<string> = [];
rl.on("line", (line) => {
if (stopped) {
return;
}
if (captureNextLine) {
captured.push(line);
captureNextLine = false;
}
if (done) {
fileStream.destroy();
rl.close();
resolveResult(captured);
stopped = true;
return;
}
if (line.startsWith("rule astj")) {
captureNextLine = true;
}
if (line.startsWith("rule mij")) {
captureNextLine = true;
done = true;
}
});
rl.on("close", () => {
resolveResult(captured);
stopped = true;
return;
}
if (line.startsWith("rule astj")) {
captureNextLine = true;
}
if (line.startsWith("rule mij")) {
captureNextLine = true;
done = true;
});
} else if (buildSystem === "rewatch") {
try {
let rewatchPath = path.resolve(
entry.project.rootPath,
"node_modules/@rolandpeelen/rewatch/rewatch"
);
const compilerArgs = JSON.parse(
cp
.execFileSync(rewatchPath, [
"--rescript-version",
entry.project.rescriptVersion,
"--get-compiler-args",
entry.file.sourceFilePath,
])
.toString()
.trim()
) as RewatchCompilerArgs;
resolveResult(compilerArgs);
} catch (e) {
console.error(e);
}
});
rl.on("close", () => {
resolveResult(captured);
});
}
});
}
function argsFromCommandString(cmdString: string): Array<Array<string>> {
const s = cmdString
.trim()
.split("command = ")[1]
.split(" ")
.map((v) => v.trim())
.filter((v) => v !== "");
const args: Array<Array<string>> = [];

for (let i = 0; i <= s.length - 1; i++) {
const item = s[i];
function argCouples(argList: string[]): string[][] {
let args: string[][] = [];
for (let i = 0; i <= argList.length - 1; i++) {
const item = argList[i];
const nextIndex = i + 1;
const nextItem = s[nextIndex] ?? "";
const nextItem = argList[nextIndex] ?? "";
if (item.startsWith("-") && nextItem.startsWith("-")) {
// Single entry arg
args.push([item]);
} else if (item.startsWith("-") && nextItem.startsWith("'")) {
// Quoted arg, take until ending '
const arg = [nextItem.slice(1)];
for (let x = nextIndex + 1; x <= s.length - 1; x++) {
let subItem = s[x];
for (let x = nextIndex + 1; x <= argList.length - 1; x++) {
let subItem = argList[x];
let break_ = false;
if (subItem.endsWith("'")) {
subItem = subItem.slice(0, subItem.length - 1);
Expand All @@ -284,6 +321,17 @@ function argsFromCommandString(cmdString: string): Array<Array<string>> {
}
return args;
}

function argsFromCommandString(cmdString: string): Array<Array<string>> {
const argList = cmdString
.trim()
.split("command = ")[1]
.split(" ")
.map((v) => v.trim())
.filter((v) => v !== "");

return argCouples(argList);
}
function removeAnsiCodes(s: string): string {
const ansiEscape = /\x1B[@-_][0-?]*[ -/]*[@-~]/g;
return s.replace(ansiEscape, "");
Expand Down Expand Up @@ -368,6 +416,7 @@ function triggerIncrementalCompilationOfFile(
incrementalFolderPath,
rescriptVersion,
},
buildRewatch: null,
buildNinja: null,
compilation: null,
killCompilationListeners: [],
Expand Down Expand Up @@ -419,11 +468,16 @@ function verifyTriggerToken(filePath: string, triggerToken: number): boolean {
async function figureOutBscArgs(entry: IncrementallyCompiledFileInfo) {
const res = await getBscArgs(entry);
if (res == null) return null;
const [astBuildCommand, fullBuildCommand] = res;

const astArgs = argsFromCommandString(astBuildCommand);
const buildArgs = argsFromCommandString(fullBuildCommand);

let astArgs: Array<Array<string>> = [];
let buildArgs: Array<Array<string>> = [];
if (Array.isArray(res)) {
const [astBuildCommand, fullBuildCommand] = res;
astArgs = argsFromCommandString(astBuildCommand);
buildArgs = argsFromCommandString(fullBuildCommand);
} else {
astArgs = argCouples(res.parser_args);
buildArgs = argCouples(res.compiler_args);
}
let callArgs: Array<string> = [];

if (config.extensionConfiguration.incrementalTypechecking?.acrossFiles) {
Expand Down