@groma/scanner-rust 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groma/scanner-rust",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Rust project evidence through established semantic tooling.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"os": [
|
|
9
|
-
"win32",
|
|
10
9
|
"linux",
|
|
11
|
-
"darwin"
|
|
10
|
+
"darwin",
|
|
11
|
+
"win32"
|
|
12
12
|
],
|
|
13
13
|
"cpu": [
|
|
14
14
|
"arm64",
|
|
@@ -42,8 +42,7 @@
|
|
|
42
42
|
}
|
|
43
43
|
],
|
|
44
44
|
"compatibility": {
|
|
45
|
-
"groma": "^0.3.0"
|
|
46
|
-
"technologyVersions": {}
|
|
45
|
+
"groma": "^0.3.0"
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// plugins/scanners/rust/src/index.ts
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import
|
|
3
|
+
import path3 from "node:path";
|
|
4
4
|
|
|
5
5
|
// packages/scanner/src/index.ts
|
|
6
6
|
function compare(...values) {
|
|
@@ -256,12 +256,73 @@ function sourcePosition(position) {
|
|
|
256
256
|
return { position: Number(position) };
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
// plugins/scanners/observations.ts
|
|
260
|
+
function combineObservations(parts) {
|
|
261
|
+
if (!parts.length)
|
|
262
|
+
return;
|
|
263
|
+
const roots = [];
|
|
264
|
+
const files = new Map;
|
|
265
|
+
const operations = [];
|
|
266
|
+
const invocations = [];
|
|
267
|
+
const diagnostics = [];
|
|
268
|
+
for (const { key, observation } of parts) {
|
|
269
|
+
const id = (value) => JSON.stringify([key, value]);
|
|
270
|
+
roots.push(...observation.roots.map((root) => ({ ...root, id: id(root.id), ...root.parent ? { parent: id(root.parent) } : {} })));
|
|
271
|
+
for (const source of observation.files) {
|
|
272
|
+
const combined = files.get(source.file) ?? { file: source.file, roots: [], symbols: [] };
|
|
273
|
+
combined.roots.push(...source.roots.map(id));
|
|
274
|
+
combined.symbols.push(...source.symbols.map((symbol) => ({ ...symbol, id: id(symbol.id) })));
|
|
275
|
+
files.set(source.file, combined);
|
|
276
|
+
}
|
|
277
|
+
operations.push(...(observation.operations ?? []).map((operation) => ({ ...operation, id: id(operation.id) })));
|
|
278
|
+
invocations.push(...(observation.invocations ?? []).map((call) => ({ ...call, source: id(call.source), targets: call.targets.map(id) })));
|
|
279
|
+
diagnostics.push(...observation.diagnostics);
|
|
280
|
+
}
|
|
281
|
+
return createScanObservation({
|
|
282
|
+
scanner: parts[0].observation.scanner,
|
|
283
|
+
roots,
|
|
284
|
+
files: [...files.values()],
|
|
285
|
+
operations,
|
|
286
|
+
invocations,
|
|
287
|
+
diagnostics
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
259
291
|
// plugins/scanners/rust/src/project.ts
|
|
260
|
-
import { execFile } from "node:child_process";
|
|
292
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
261
293
|
import { access } from "node:fs/promises";
|
|
294
|
+
import path2 from "node:path";
|
|
295
|
+
import { promisify as promisify2 } from "node:util";
|
|
296
|
+
|
|
297
|
+
// plugins/scanners/projects.ts
|
|
298
|
+
import { execFile } from "node:child_process";
|
|
299
|
+
import { existsSync } from "node:fs";
|
|
262
300
|
import path from "node:path";
|
|
263
301
|
import { promisify } from "node:util";
|
|
264
302
|
var execute = promisify(execFile);
|
|
303
|
+
var excluded = new Set([
|
|
304
|
+
".git",
|
|
305
|
+
"node_modules",
|
|
306
|
+
"vendor",
|
|
307
|
+
"target",
|
|
308
|
+
"dist",
|
|
309
|
+
"build",
|
|
310
|
+
"bin",
|
|
311
|
+
"obj",
|
|
312
|
+
".gradle",
|
|
313
|
+
".angular",
|
|
314
|
+
"coverage",
|
|
315
|
+
"generated",
|
|
316
|
+
"groma",
|
|
317
|
+
".groma"
|
|
318
|
+
]);
|
|
319
|
+
async function projectFiles(root, matches) {
|
|
320
|
+
const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
|
|
321
|
+
return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && !file.split("/").some((part) => excluded.has(part)) && existsSync(path.join(root, file))))].sort();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// plugins/scanners/rust/src/project.ts
|
|
325
|
+
var execute2 = promisify2(execFile2);
|
|
265
326
|
async function exists(file) {
|
|
266
327
|
try {
|
|
267
328
|
await access(file);
|
|
@@ -272,59 +333,81 @@ async function exists(file) {
|
|
|
272
333
|
}
|
|
273
334
|
function manifestAt(root, settings) {
|
|
274
335
|
if (settings.manifest === undefined)
|
|
275
|
-
return
|
|
336
|
+
return path2.join(root, "Cargo.toml");
|
|
276
337
|
if (typeof settings.manifest !== "string") {
|
|
277
338
|
throw new Error("RUST_PROJECT_SELECTION: Set settings.manifest on the rust entry in scanners.json to a Cargo.toml path.");
|
|
278
339
|
}
|
|
279
|
-
return
|
|
340
|
+
return path2.resolve(root, settings.manifest);
|
|
280
341
|
}
|
|
281
|
-
async function readRustProject(root, settings, options) {
|
|
342
|
+
async function readRustProject(root, settings, options, workspace = false) {
|
|
282
343
|
const manifest = manifestAt(root, settings);
|
|
283
344
|
if (!await exists(manifest)) {
|
|
284
345
|
throw new Error("RUST_PROJECT_MISSING: Select an existing Cargo.toml with settings.manifest on the rust entry in scanners.json.");
|
|
285
346
|
}
|
|
286
347
|
let metadata;
|
|
287
348
|
try {
|
|
288
|
-
const result = await
|
|
349
|
+
const result = await execute2(options.cargo ?? "cargo", ["metadata", "--format-version", "1", "--offline", "--locked", "--manifest-path", manifest], { cwd: path2.dirname(manifest), maxBuffer: 64 * 1024 * 1024 });
|
|
289
350
|
metadata = JSON.parse(result.stdout);
|
|
290
351
|
} catch (error) {
|
|
291
352
|
throw new Error(`RUST_CARGO_NOT_READY: Install the project's Rust toolchain, then run cargo fetch --manifest-path "${manifest}" to prepare its lockfile and dependencies. ${error}`);
|
|
292
353
|
}
|
|
293
354
|
const packageAtManifest = metadata.packages.find((pkg) => pkg.manifest_path === manifest);
|
|
294
|
-
const packages = packageAtManifest ? [packageAtManifest] : metadata.packages.filter((pkg) => metadata.workspace_members.includes(pkg.id));
|
|
355
|
+
const packages = packageAtManifest && !workspace ? [packageAtManifest] : metadata.packages.filter((pkg) => metadata.workspace_members.includes(pkg.id));
|
|
295
356
|
const targets = packages.flatMap((pkg) => pkg.targets).filter((target) => target.kind.some((kind) => kind === "lib" || kind === "bin")).map((target) => target.src_path).sort();
|
|
296
357
|
if (!targets.length)
|
|
297
358
|
throw new Error("RUST_TARGET_MISSING: The selected Cargo project needs a library or binary target.");
|
|
298
|
-
return { root, manifest, name: packageAtManifest?.name ??
|
|
359
|
+
return { root, manifest, name: packageAtManifest?.name ?? path2.basename(path2.dirname(manifest)), targets };
|
|
299
360
|
}
|
|
300
361
|
async function checkRustToolchain(root, options) {
|
|
301
362
|
try {
|
|
302
|
-
await
|
|
303
|
-
const result = await
|
|
304
|
-
const library =
|
|
363
|
+
await execute2(options.cargo ?? "cargo", ["--version"], { cwd: root });
|
|
364
|
+
const result = await execute2(options.rustc ?? "rustc", ["--print", "sysroot"], { cwd: root });
|
|
365
|
+
const library = path2.join(result.stdout.trim(), "lib/rustlib/src/rust/library");
|
|
305
366
|
if (!await exists(library))
|
|
306
367
|
throw new Error("Rust standard library sources are missing.");
|
|
307
368
|
} catch (error) {
|
|
308
369
|
throw new Error(`RUST_TOOLCHAIN_MISSING: Install the project's Rust toolchain with Cargo and rustc, and add its standard library sources with rustup component add rust-src. ${error}`);
|
|
309
370
|
}
|
|
310
371
|
}
|
|
372
|
+
async function rustProjects(root, settings, options = {}) {
|
|
373
|
+
if (settings.manifest !== undefined)
|
|
374
|
+
return [manifestAt(root, settings)];
|
|
375
|
+
const selected = new Set;
|
|
376
|
+
const covered = new Set;
|
|
377
|
+
for (const file of await projectFiles(root, (file) => path2.posix.basename(file) === "Cargo.toml")) {
|
|
378
|
+
const manifest = path2.resolve(root, file);
|
|
379
|
+
if (covered.has(manifest))
|
|
380
|
+
continue;
|
|
381
|
+
const { stdout } = await execute2(options.cargo ?? "cargo", ["metadata", "--no-deps", "--format-version", "1", "--offline", "--locked", "--manifest-path", manifest], { cwd: path2.dirname(manifest), maxBuffer: 64 * 1024 * 1024 });
|
|
382
|
+
const metadata = JSON.parse(stdout);
|
|
383
|
+
selected.add(path2.join(metadata.workspace_root, "Cargo.toml"));
|
|
384
|
+
for (const pkg of metadata.packages)
|
|
385
|
+
if (metadata.workspace_members.includes(pkg.id))
|
|
386
|
+
covered.add(pkg.manifest_path);
|
|
387
|
+
covered.add(path2.join(metadata.workspace_root, "Cargo.toml"));
|
|
388
|
+
}
|
|
389
|
+
return [...selected].sort();
|
|
390
|
+
}
|
|
311
391
|
|
|
312
392
|
// plugins/scanners/rust/src/index.ts
|
|
313
393
|
var executable = fileURLToPath(new URL(`../dist/bin/${process.platform}-${process.arch}/groma-rust-scanner${process.platform === "win32" ? ".exe" : ""}`, import.meta.url));
|
|
314
|
-
async function checkRustReadiness(repositoryRoot, settings = {}, options = {}) {
|
|
315
|
-
const root =
|
|
394
|
+
async function checkRustReadiness(repositoryRoot, settings = {}, options = {}, workspace = false) {
|
|
395
|
+
const root = path3.resolve(repositoryRoot);
|
|
316
396
|
const worker = options.worker ?? executable;
|
|
317
397
|
if (!await exists(worker)) {
|
|
318
398
|
throw new Error("RUST_WORKER_MISSING: Install the packaged Rust scanner, or build it with bun plugins/scanners/rust/build.ts.");
|
|
319
399
|
}
|
|
320
|
-
await checkRustToolchain(root, options);
|
|
321
|
-
return { input: await readRustProject(root, settings, options), worker };
|
|
400
|
+
await checkRustToolchain(path3.dirname(path3.resolve(root, typeof settings.manifest === "string" ? settings.manifest : "Cargo.toml")), options);
|
|
401
|
+
return { input: await readRustProject(root, settings, options, workspace), worker };
|
|
322
402
|
}
|
|
323
403
|
async function scanRustSource(repositoryRoot, settings = {}, options = {}) {
|
|
324
404
|
const { input, worker } = await checkRustReadiness(repositoryRoot, settings, options);
|
|
405
|
+
return runRust(input, worker);
|
|
406
|
+
}
|
|
407
|
+
async function runRust(input, worker) {
|
|
325
408
|
try {
|
|
326
|
-
const pending =
|
|
327
|
-
cwd: input.
|
|
409
|
+
const pending = execute2(worker, [], {
|
|
410
|
+
cwd: path3.dirname(input.manifest),
|
|
328
411
|
encoding: "utf8",
|
|
329
412
|
timeout: 120000,
|
|
330
413
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -351,10 +434,23 @@ var scanner = {
|
|
|
351
434
|
],
|
|
352
435
|
exclude: ["**/target/**", "**/node_modules/**", "**/.git/**", "**/vendor/**", "**/dist/**"]
|
|
353
436
|
},
|
|
354
|
-
checkReadiness: async (root, settings) => {
|
|
355
|
-
await
|
|
437
|
+
checkReadiness: async (root, settings = {}) => {
|
|
438
|
+
const projects = await rustProjects(root, settings);
|
|
439
|
+
if (!projects.length)
|
|
440
|
+
throw new Error("RUST_PROJECT_MISSING: No Cargo.toml was found.");
|
|
441
|
+
for (const manifest of projects)
|
|
442
|
+
await checkRustReadiness(root, { ...settings, manifest }, {}, settings.manifest === undefined);
|
|
356
443
|
},
|
|
357
|
-
scan:
|
|
444
|
+
scan: async (root, settings = {}) => {
|
|
445
|
+
const parts = [];
|
|
446
|
+
for (const manifest of await rustProjects(root, settings)) {
|
|
447
|
+
const options = { ...settings, manifest };
|
|
448
|
+
const { input, worker } = await checkRustReadiness(root, options, {}, settings.manifest === undefined);
|
|
449
|
+
const observation = await runRust(input, worker);
|
|
450
|
+
parts.push({ key: path3.relative(root, manifest).split(path3.sep).join("/"), observation });
|
|
451
|
+
}
|
|
452
|
+
return combineObservations(parts);
|
|
453
|
+
}
|
|
358
454
|
};
|
|
359
455
|
var src_default = scanner;
|
|
360
456
|
export {
|