Skip to content

Commit 0f4acf9

Browse files
authored
Merge pull request #1125 from nojaf/rescript-runtime
Add @rescript/runtime when finding bsc arguments
2 parents ae03be4 + 82b656a commit 0f4acf9

File tree

13 files changed

+478
-224
lines changed

13 files changed

+478
-224
lines changed

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
server/out
33
analysis/examples
44
analysis/reanalyze/examples
5-
tools/tests
5+
tools/tests
6+
.history/

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
- Add status bar item tracking compilation state. https://github.com/rescript-lang/rescript-vscode/pull/1119
2323

24+
#### :house: Internal
25+
26+
- Find `@rescript/runtime` for Rewatch compiler-args call. https://github.com/rescript-lang/rescript-vscode/pull/1125
27+
2428
## 1.64.0
2529

2630
#### :rocket: New Feature

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@
1010

1111
## Contents
1212

13+
- [Contents](#contents)
1314
- [📝 Prerequisite](#-prerequisite)
1415
- [🌈 Supported Themes](#-supported-themes)
1516
- [💡 Features](#-features)
1617
- [📥 Installation](#-installation)
18+
- [Pre-release channel](#pre-release-channel)
1719
- [📦 Commands](#-commands)
1820
- [🔨 Settings](#-settings)
1921
- [🚀 Code Analyzer](#-code-analyzer)
2022
- [Configuring the Code Analyzer](#configuring-the-code-analyzer)
2123
- [Usage](#usage)
2224
- [Caveats](#caveats)
23-
- [🪄 Tips & Tricks](#-tips--tricks)
25+
- [🪄 Tips \& Tricks](#-tips--tricks)
2426
- [Hide generated files](#hide-generated-files)
25-
- [⌨️ Use with Other Editors](#️-use-with-other-editors)
2627
- [📰 Changelog](#-changelog)
2728
- [👏 How to Contribute](#-how-to-contribute)
2829
- [📄 License](#-license)
@@ -102,6 +103,7 @@ You'll find all ReScript specific settings under the scope `rescript.settings`.
102103
| Prompt to Start Build | If there's no ReScript build running already in the opened project, the extension will prompt you and ask if you want to start a build automatically. You can turn off this automatic prompt via the setting `rescript.settings.askToStartBuild`. |
103104
| ReScript Binary Path | The extension will look for the existence of a `node_modules/.bin/rescript` file and use its directory as the `binaryPath`. If it does not find it at the project root (which is where the nearest `rescript.json` resides), it goes up folders in the filesystem recursively until it either finds it (often the case in monorepos) or hits the top level. To override this lookup process, the path can be configured explicitly using the setting `rescript.settings.binaryPath` |
104105
| ReScript Platform Path | The extension will look for the existence of a `node_modules/rescript` directory and use the subdirectory corresponding to the current platform as the `platformPath`. If it does not find it at the project root (which is where the nearest `rescript.json` resides), it goes up folders in the filesystem recursively until it either finds it (often the case in monorepos) or hits the top level. To override this lookup process, the path can be configured explicitly using the setting `rescript.settings.platformPath` |
106+
| ReScript Runtime Path | The extension will look for the existence of a `node_modules/@rescript/runtime` directory (ReScript v12 beta 11+). To override this lookup process, the path can be configured explicitly using the setting `rescript.settings.runtimePath`. |
105107
| Inlay Hints (experimental) | This allows an editor to place annotations inline with text to display type hints. Enable using `rescript.settings.inlayHints.enable: true` |
106108
| Code Lens (experimental) | This tells the editor to add code lenses to function definitions, showing its full type above the definition. Enable using `rescript.settings.codeLens: true` |
107109
| Signature Help | This tells the editor to show signature help when you're writing function calls. Enable using `rescript.settings.signatureHelp.enabled: true` |

package-lock.json

Lines changed: 16 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,14 @@
207207
"default": null,
208208
"description": "Path to the directory where platform-specific ReScript binaries are. You can use it if you haven't or don't want to use the installed ReScript from node_modules in your project."
209209
},
210+
"rescript.settings.runtimePath": {
211+
"type": [
212+
"string",
213+
"null"
214+
],
215+
"default": null,
216+
"description": "Optional path to the directory containing the @rescript/runtime package. Set this if your tooling is unable to automatically locate the package in your project."
217+
},
210218
"rescript.settings.compileStatus.enable": {
211219
"type": "boolean",
212220
"default": true,
@@ -259,7 +267,7 @@
259267
"bundle": "npm run bundle-server && npm run bundle-client"
260268
},
261269
"devDependencies": {
262-
"@types/node": "^14.14.41",
270+
"@types/node": "^20.19.13",
263271
"@types/semver": "^7.7.0",
264272
"@types/vscode": "1.68.0",
265273
"esbuild": "^0.20.1",
@@ -268,5 +276,6 @@
268276
},
269277
"dependencies": {
270278
"semver": "^7.7.2"
271-
}
279+
},
280+
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
272281
}

scripts/find-runtime.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// benchmark
2+
const start = process.hrtime.bigint();
3+
4+
// start code
5+
const args = process.argv.slice(2);
6+
7+
if (args.length === 0) {
8+
console.log(`
9+
Usage: node find-runtime.mjs <project-folder>
10+
Find @rescript/runtime directories in a project's node_modules.
11+
Arguments:
12+
project-folder Path to the project directory to search
13+
Examples:
14+
node find-runtime.mjs /path/to/project
15+
node find-runtime.mjs .
16+
`);
17+
process.exit(1);
18+
}
19+
20+
const project = args[args.length - 1];
21+
22+
import { findRescriptRuntimesInProject } from "../server/src/find-runtime.ts";
23+
24+
const runtimes = await findRescriptRuntimesInProject(project);
25+
26+
console.log("Found @rescript/runtime directories:", runtimes);
27+
28+
// end code
29+
const end = process.hrtime.bigint();
30+
const durationMs = Number(end - start) / 1e6; // convert ns → ms
31+
32+
console.log(`Script took ${durationMs.toFixed(3)}ms`);

server/src/bsc-args/bsb.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as path from "path";
2+
import fs from "fs";
3+
import { IncrementallyCompiledFileInfo } from "../incrementalCompilation";
4+
import { buildNinjaPartialPath } from "../constants";
5+
6+
export type BsbCompilerArgs = string[];
7+
8+
export async function getBsbBscArgs(
9+
entry: IncrementallyCompiledFileInfo,
10+
): Promise<BsbCompilerArgs | null> {
11+
const buildNinjaPath = path.resolve(
12+
entry.project.rootPath,
13+
buildNinjaPartialPath,
14+
);
15+
16+
let stat: fs.Stats;
17+
try {
18+
stat = await fs.promises.stat(buildNinjaPath);
19+
} catch {
20+
return null;
21+
}
22+
23+
const cache = entry.buildNinja;
24+
if (cache && cache.fileMtime >= stat.mtimeMs) {
25+
return cache.rawExtracted;
26+
}
27+
28+
const fh = await fs.promises.open(buildNinjaPath, "r");
29+
try {
30+
let captureNext = false;
31+
let haveAst = false;
32+
const captured: string[] = [];
33+
34+
for await (const rawLine of fh.readLines()) {
35+
const line = String(rawLine).trim();
36+
if (captureNext) {
37+
captured.push(line);
38+
captureNext = false;
39+
if (haveAst && captured.length === 2) break; // got ast + mij
40+
}
41+
if (line.startsWith("rule astj")) {
42+
captureNext = true;
43+
haveAst = true;
44+
} else if (line.startsWith("rule mij")) {
45+
captureNext = true;
46+
}
47+
}
48+
49+
if (captured.length !== 2) return null;
50+
51+
entry.buildNinja = {
52+
fileMtime: stat.mtimeMs,
53+
rawExtracted: captured,
54+
};
55+
return captured;
56+
} finally {
57+
await fh.close();
58+
}
59+
}

server/src/bsc-args/rewatch.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import * as path from "path";
2+
import * as utils from "../utils";
3+
import * as cp from "node:child_process";
4+
import * as p from "vscode-languageserver-protocol";
5+
import semver from "semver";
6+
import {
7+
debug,
8+
IncrementallyCompiledFileInfo,
9+
} from "../incrementalCompilation";
10+
import type { projectFiles } from "../projectFiles";
11+
import config from "../config";
12+
import { findRescriptRuntimesInProject } from "../find-runtime";
13+
import { jsonrpcVersion } from "../constants";
14+
15+
export type RewatchCompilerArgs = {
16+
compiler_args: Array<string>;
17+
parser_args: Array<string>;
18+
};
19+
20+
async function getRuntimePath(
21+
entry: IncrementallyCompiledFileInfo,
22+
): Promise<string | null> {
23+
let rescriptRuntime: string | null =
24+
config.extensionConfiguration.runtimePath ?? null;
25+
26+
if (rescriptRuntime !== null) {
27+
if (debug()) {
28+
console.log(
29+
`Using configured runtime path as RESCRIPT_RUNTIME: ${rescriptRuntime}`,
30+
);
31+
}
32+
return rescriptRuntime;
33+
}
34+
35+
const rescriptRuntimes = await findRescriptRuntimesInProject(
36+
entry.project.workspaceRootPath,
37+
);
38+
39+
if (debug()) {
40+
if (rescriptRuntimes.length === 0) {
41+
console.log(
42+
`Did not find @rescript/runtime directory for ${entry.project.workspaceRootPath}`,
43+
);
44+
} else if (rescriptRuntimes.length > 1) {
45+
console.warn(
46+
`Found multiple @rescript/runtime directories, using the first one as RESCRIPT_RUNTIME: ${rescriptRuntimes.join(", ")}`,
47+
);
48+
} else {
49+
console.log(
50+
`Found @rescript/runtime directory: ${rescriptRuntimes.join(", ")}`,
51+
);
52+
}
53+
}
54+
55+
return rescriptRuntimes.at(0) ?? null;
56+
}
57+
58+
export async function getRewatchBscArgs(
59+
send: (msg: p.Message) => void,
60+
projectsFiles: Map<string, projectFiles>,
61+
entry: IncrementallyCompiledFileInfo,
62+
): Promise<RewatchCompilerArgs | null> {
63+
const rewatchCacheEntry = entry.buildRewatch;
64+
65+
if (
66+
rewatchCacheEntry != null &&
67+
rewatchCacheEntry.lastFile === entry.file.sourceFilePath
68+
) {
69+
return Promise.resolve(rewatchCacheEntry.compilerArgs);
70+
}
71+
72+
try {
73+
const project = projectsFiles.get(entry.project.rootPath);
74+
if (project?.rescriptVersion == null) return null;
75+
let rewatchPath = path.resolve(
76+
entry.project.workspaceRootPath,
77+
"node_modules/@rolandpeelen/rewatch/rewatch",
78+
);
79+
let rescriptRewatchPath = null;
80+
if (
81+
semver.valid(project.rescriptVersion) &&
82+
semver.satisfies(project.rescriptVersion as string, ">11", {
83+
includePrerelease: true,
84+
})
85+
) {
86+
rescriptRewatchPath = await utils.findRewatchBinary(
87+
entry.project.workspaceRootPath,
88+
);
89+
}
90+
91+
if (
92+
semver.valid(project.rescriptVersion) &&
93+
semver.satisfies(project.rescriptVersion as string, ">=12.0.0-beta.1", {
94+
includePrerelease: true,
95+
})
96+
) {
97+
rescriptRewatchPath = await utils.findRescriptExeBinary(
98+
entry.project.workspaceRootPath,
99+
);
100+
}
101+
102+
if (rescriptRewatchPath != null) {
103+
rewatchPath = rescriptRewatchPath;
104+
if (debug()) {
105+
console.log(
106+
`Found rewatch binary bundled with v12: ${rescriptRewatchPath}`,
107+
);
108+
}
109+
} else {
110+
if (debug()) {
111+
console.log("Did not find rewatch binary bundled with v12");
112+
}
113+
}
114+
115+
const rewatchArguments = semver.satisfies(
116+
project.rescriptVersion,
117+
">=12.0.0-beta.2",
118+
{ includePrerelease: true },
119+
)
120+
? ["compiler-args", entry.file.sourceFilePath]
121+
: [
122+
"--rescript-version",
123+
project.rescriptVersion,
124+
"--compiler-args",
125+
entry.file.sourceFilePath,
126+
];
127+
const bscExe = await utils.findBscExeBinary(
128+
entry.project.workspaceRootPath,
129+
);
130+
const env: NodeJS.ProcessEnv = {};
131+
if (bscExe != null) {
132+
env["RESCRIPT_BSC_EXE"] = bscExe;
133+
}
134+
135+
// For ReScript >= 12.0.0-beta.11 we need to set RESCRIPT_RUNTIME
136+
if (
137+
semver.satisfies(project.rescriptVersion, ">=12.0.0-beta.11", {
138+
includePrerelease: true,
139+
})
140+
) {
141+
let rescriptRuntime: string | null = await getRuntimePath(entry);
142+
143+
if (rescriptRuntime !== null) {
144+
env["RESCRIPT_RUNTIME"] = rescriptRuntime;
145+
} else {
146+
// If no runtime was found, we should let the user know.
147+
let params: p.ShowMessageParams = {
148+
type: p.MessageType.Error,
149+
message:
150+
`[Incremental type checking] The @rescript/runtime package was not found in your project. ` +
151+
`It is normally included with ReScript, but either it's missing or could not be detected. ` +
152+
`Check that it exists in your dependencies, or configure 'rescript.settings.runtimePath' to point to it. ` +
153+
`Without this package, incremental type checking may not work as expected.`,
154+
};
155+
let message: p.NotificationMessage = {
156+
jsonrpc: jsonrpcVersion,
157+
method: "window/showMessage",
158+
params: params,
159+
};
160+
send(message);
161+
}
162+
}
163+
164+
const compilerArgs = JSON.parse(
165+
cp.execFileSync(rewatchPath, rewatchArguments, { env }).toString().trim(),
166+
) as RewatchCompilerArgs;
167+
168+
entry.buildRewatch = {
169+
lastFile: entry.file.sourceFilePath,
170+
compilerArgs: compilerArgs,
171+
};
172+
173+
return compilerArgs;
174+
} catch (e) {
175+
console.error(e);
176+
return null;
177+
}
178+
}

0 commit comments

Comments
 (0)