@joshmossas/nx-cargo 0.6.2
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.
- package/.babelrc +3 -0
- package/.eslintrc.json +34 -0
- package/README.md +39 -0
- package/build.config.ts +10 -0
- package/dist/index.cjs +100 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +79 -0
- package/executors.json +25 -0
- package/generators.json +25 -0
- package/jest.config.ts +17 -0
- package/package.json +30 -0
- package/project.json +53 -0
- package/src/common/index.spec.ts +183 -0
- package/src/common/index.ts +358 -0
- package/src/common/schema.d.ts +111 -0
- package/src/executors/build/executor.ts +21 -0
- package/src/executors/build/schema.d.ts +39 -0
- package/src/executors/build/schema.json +77 -0
- package/src/executors/clippy/executor.ts +18 -0
- package/src/executors/clippy/schema.d.ts +34 -0
- package/src/executors/clippy/schema.json +29 -0
- package/src/executors/run/executor.ts +19 -0
- package/src/executors/run/schema.d.ts +32 -0
- package/src/executors/run/schema.json +77 -0
- package/src/executors/test/executor.ts +19 -0
- package/src/executors/test/schema.d.ts +22 -0
- package/src/executors/test/schema.json +73 -0
- package/src/generators/binary/files/Cargo.toml__template__ +8 -0
- package/src/generators/binary/files/src/main.rs__template__ +3 -0
- package/src/generators/binary/generator.spec.ts +75 -0
- package/src/generators/binary/generator.ts +76 -0
- package/src/generators/binary/schema.d.ts +6 -0
- package/src/generators/binary/schema.json +35 -0
- package/src/generators/init/files/Cargo.toml +2 -0
- package/src/generators/init/files/rust-toolchain.toml__template__ +2 -0
- package/src/generators/init/files/rustfmt.toml +0 -0
- package/src/generators/init/generator.spec.ts +49 -0
- package/src/generators/init/generator.ts +55 -0
- package/src/generators/init/schema.d.ts +7 -0
- package/src/generators/init/schema.json +14 -0
- package/src/generators/library/files/Cargo.toml__template__ +8 -0
- package/src/generators/library/files/src/lib.rs__template__ +13 -0
- package/src/generators/library/generator.spec.ts +96 -0
- package/src/generators/library/generator.ts +78 -0
- package/src/generators/library/schema.d.ts +6 -0
- package/src/generators/library/schema.json +35 -0
- package/src/graph/index.ts +189 -0
- package/src/index.ts +1 -0
- package/tsconfig.json +13 -0
- package/tsconfig.lib.json +12 -0
- package/tsconfig.spec.json +20 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Tree } from "@nx/devkit";
|
|
2
|
+
import { createTreeWithEmptyWorkspace } from "@nx/devkit/testing";
|
|
3
|
+
import runGenerator from "./generator";
|
|
4
|
+
|
|
5
|
+
describe("library generator", () => {
|
|
6
|
+
let appTree: Tree;
|
|
7
|
+
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
appTree = createTreeWithEmptyWorkspace({ layout: "apps-libs" });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("with kebab-case project name", () => {
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
await runGenerator(appTree, { name: "my-library" });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should create the correct file structure", () => {
|
|
18
|
+
let changes = appTree.listChanges();
|
|
19
|
+
let cargoToml = changes.find(c => c.path === "libs/my-library/Cargo.toml");
|
|
20
|
+
let libRs = changes.find(c => c.path === "libs/my-library/src/lib.rs");
|
|
21
|
+
|
|
22
|
+
expect(cargoToml).toBeTruthy();
|
|
23
|
+
expect(libRs).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should populate project files with the correct content", () => {
|
|
27
|
+
let changes = appTree.listChanges();
|
|
28
|
+
let cargoContent = changes
|
|
29
|
+
.find(c => c.path === "libs/my-library/Cargo.toml")!
|
|
30
|
+
.content!.toString();
|
|
31
|
+
|
|
32
|
+
expect(cargoContent).toContain(`name = "my-library"`);
|
|
33
|
+
expect(cargoContent).toContain(`edition = "2021"`);
|
|
34
|
+
|
|
35
|
+
let libRsContent = changes
|
|
36
|
+
.find(c => c.path === "libs/my-library/src/lib.rs")!
|
|
37
|
+
.content!.toString();
|
|
38
|
+
|
|
39
|
+
expect(libRsContent).toContain(`pub fn my_library() -> String {`);
|
|
40
|
+
expect(libRsContent).toContain(`"my-library".into()`);
|
|
41
|
+
expect(libRsContent).toContain(
|
|
42
|
+
`assert_eq!(my_library(), "my-library".to_string())`
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should add project to workspace members", () => {
|
|
47
|
+
let changes = appTree.listChanges();
|
|
48
|
+
let members = changes.find(c => c.path === "Cargo.toml")!.content!.toString();
|
|
49
|
+
|
|
50
|
+
expect(members).toContain(`"libs/my-library"`);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("with snake_case project name", () => {
|
|
55
|
+
beforeAll(async () => {
|
|
56
|
+
appTree = createTreeWithEmptyWorkspace({ layout: "apps-libs" });
|
|
57
|
+
await runGenerator(appTree, { name: "my_library" });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should create the correct file structure", () => {
|
|
61
|
+
let changes = appTree.listChanges();
|
|
62
|
+
let cargoToml = changes.find(c => c.path === "libs/my_library/Cargo.toml");
|
|
63
|
+
let libRs = changes.find(c => c.path === "libs/my_library/src/lib.rs");
|
|
64
|
+
|
|
65
|
+
expect(cargoToml).toBeTruthy();
|
|
66
|
+
expect(libRs).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should populate project files with the correct content", () => {
|
|
70
|
+
let changes = appTree.listChanges();
|
|
71
|
+
let cargoContent = changes
|
|
72
|
+
.find(c => c.path === "libs/my_library/Cargo.toml")!
|
|
73
|
+
.content!.toString();
|
|
74
|
+
|
|
75
|
+
expect(cargoContent).toContain(`name = "my_library"`);
|
|
76
|
+
expect(cargoContent).toContain(`edition = "2021"`);
|
|
77
|
+
|
|
78
|
+
let libRsContent = changes
|
|
79
|
+
.find(c => c.path === "libs/my_library/src/lib.rs")!
|
|
80
|
+
.content!.toString();
|
|
81
|
+
|
|
82
|
+
expect(libRsContent).toContain(`pub fn my_library() -> String {`);
|
|
83
|
+
expect(libRsContent).toContain(`"my_library".into()`);
|
|
84
|
+
expect(libRsContent).toContain(
|
|
85
|
+
`assert_eq!(my_library(), "my_library".to_string())`
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should add project to workspace members", () => {
|
|
90
|
+
let changes = appTree.listChanges();
|
|
91
|
+
let members = changes.find(c => c.path === "Cargo.toml")!.content!.toString();
|
|
92
|
+
|
|
93
|
+
expect(members).toContain(`"libs/my_library"`);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Tree,
|
|
3
|
+
addProjectConfiguration,
|
|
4
|
+
formatFiles,
|
|
5
|
+
generateFiles,
|
|
6
|
+
} from "@nx/devkit";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
GeneratorOptions,
|
|
11
|
+
normalizeGeneratorOptions,
|
|
12
|
+
updateWorkspaceMembers,
|
|
13
|
+
} from "../../common";
|
|
14
|
+
import cargoInit from "../init/generator";
|
|
15
|
+
import CLIOptions from "./schema";
|
|
16
|
+
|
|
17
|
+
// prettier-ignore
|
|
18
|
+
type Options = CLIOptions & GeneratorOptions;
|
|
19
|
+
|
|
20
|
+
export default async function (host: Tree, opts: CLIOptions) {
|
|
21
|
+
let options = normalizeGeneratorOptions("library", host, opts);
|
|
22
|
+
|
|
23
|
+
addProjectConfiguration(host, options.projectName, {
|
|
24
|
+
root: options.projectRoot,
|
|
25
|
+
projectType: "library",
|
|
26
|
+
sourceRoot: `${options.projectRoot}/src`,
|
|
27
|
+
targets: {
|
|
28
|
+
build: {
|
|
29
|
+
executor: "@nxrs/cargo:build",
|
|
30
|
+
options: {
|
|
31
|
+
profile: "dev",
|
|
32
|
+
},
|
|
33
|
+
configurations: {
|
|
34
|
+
production: {
|
|
35
|
+
profile: "release",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
test: {
|
|
40
|
+
executor: "@nxrs/cargo:test",
|
|
41
|
+
options: {},
|
|
42
|
+
},
|
|
43
|
+
lint: {
|
|
44
|
+
executor: "@nxrs/cargo:clippy",
|
|
45
|
+
options: {
|
|
46
|
+
fix: false,
|
|
47
|
+
failOnWarnings: true,
|
|
48
|
+
noDeps: true,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
tags: options.parsedTags,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await addFiles(host, options);
|
|
56
|
+
updateWorkspaceMembers(host, options);
|
|
57
|
+
await formatFiles(host);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function addFiles(host: Tree, opts: Options) {
|
|
61
|
+
if (!host.exists("Cargo.toml")) {
|
|
62
|
+
await cargoInit(host, {});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let substitutions = {
|
|
66
|
+
projectName: opts.projectName,
|
|
67
|
+
moduleName: opts.moduleName,
|
|
68
|
+
edition: opts.edition,
|
|
69
|
+
template: "",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
generateFiles(
|
|
73
|
+
host,
|
|
74
|
+
path.join(__dirname, "files"),
|
|
75
|
+
opts.projectRoot,
|
|
76
|
+
substitutions
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"id": "Library",
|
|
4
|
+
"title": "",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "",
|
|
10
|
+
"$default": {
|
|
11
|
+
"$source": "argv",
|
|
12
|
+
"index": 0
|
|
13
|
+
},
|
|
14
|
+
"x-prompt": "What name would you like to use?"
|
|
15
|
+
},
|
|
16
|
+
"edition": {
|
|
17
|
+
"type": "number",
|
|
18
|
+
"description": "What Rust edition to use",
|
|
19
|
+
"default": 2021
|
|
20
|
+
},
|
|
21
|
+
"tags": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Add tags to the project (used for linting)",
|
|
24
|
+
"alias": "t"
|
|
25
|
+
},
|
|
26
|
+
"directory": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "A directory where the project is placed",
|
|
29
|
+
"alias": "d"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"required": [
|
|
33
|
+
"name"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProjectConfiguration,
|
|
3
|
+
CreateDependenciesContext as Context,
|
|
4
|
+
RawProjectGraphDependency as GraphDependency,
|
|
5
|
+
DependencyType,
|
|
6
|
+
} from "@nx/devkit";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import * as cp from "node:child_process";
|
|
9
|
+
import * as os from "node:os";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
|
|
12
|
+
type VersionNumber = `${number}.${number}.${number}`;
|
|
13
|
+
type PackageVersion = `${string}@${VersionNumber}` | VersionNumber;
|
|
14
|
+
type CargoId = `${"registry" | "path"}+${
|
|
15
|
+
| "http"
|
|
16
|
+
| "https"
|
|
17
|
+
| "file"}://${string}#${PackageVersion}`;
|
|
18
|
+
|
|
19
|
+
interface CargoPackage {
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
id: CargoId;
|
|
23
|
+
license: string;
|
|
24
|
+
license_file: string | null;
|
|
25
|
+
description: string;
|
|
26
|
+
source: string | null;
|
|
27
|
+
dependencies: CargoDependency[];
|
|
28
|
+
targets: unknown; // TODO
|
|
29
|
+
features: Record<string, string[]>;
|
|
30
|
+
manifest_path: string;
|
|
31
|
+
metadata: unknown | null; // TODO
|
|
32
|
+
publish: unknown | null; // TODO
|
|
33
|
+
authors: string[];
|
|
34
|
+
categories: string[];
|
|
35
|
+
keywords: string[];
|
|
36
|
+
readme: string | null;
|
|
37
|
+
repository: string | null;
|
|
38
|
+
homepage: string | null;
|
|
39
|
+
documentation: string | null;
|
|
40
|
+
edition: string;
|
|
41
|
+
links: unknown | null; // TODO
|
|
42
|
+
default_run: unknown | null; // TODO
|
|
43
|
+
rust_version: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface CargoDependency {
|
|
47
|
+
name: string;
|
|
48
|
+
source: string | null;
|
|
49
|
+
req: string;
|
|
50
|
+
kind: "build" | "dev" | null;
|
|
51
|
+
rename: string | null;
|
|
52
|
+
optional: boolean;
|
|
53
|
+
uses_default_features: boolean;
|
|
54
|
+
features: string[];
|
|
55
|
+
target: string | null;
|
|
56
|
+
registry: string | null;
|
|
57
|
+
path?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface CargoMetadata {
|
|
61
|
+
packages: CargoPackage[];
|
|
62
|
+
workspace_members: CargoId[];
|
|
63
|
+
workspace_default_members: CargoId[];
|
|
64
|
+
resolve: {
|
|
65
|
+
nodes: ResolveNode[];
|
|
66
|
+
root: unknown;
|
|
67
|
+
};
|
|
68
|
+
target_directory: string;
|
|
69
|
+
version: number;
|
|
70
|
+
workspace_root: string;
|
|
71
|
+
metadata: unknown | null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface ResolveNode {
|
|
75
|
+
id: CargoId;
|
|
76
|
+
dependencies: CargoId[];
|
|
77
|
+
}
|
|
78
|
+
export function createDependencies(_: unknown, ctx: Context): GraphDependency[] {
|
|
79
|
+
const allDependencies: GraphDependency[] = [];
|
|
80
|
+
const processedWorkspaceRoots = new Set<string>();
|
|
81
|
+
|
|
82
|
+
// 1. Identify all potential Cargo workspaces/projects in the Nx graph
|
|
83
|
+
const cargoConfigPaths = Object.values(ctx.projects)
|
|
84
|
+
.map(p => path.join(ctx.workspaceRoot, p.root, "Cargo.toml"))
|
|
85
|
+
.filter(p => fs.existsSync(p));
|
|
86
|
+
|
|
87
|
+
for (const configPath of cargoConfigPaths) {
|
|
88
|
+
const configDir = path.dirname(configPath);
|
|
89
|
+
|
|
90
|
+
// 2. Get metadata for this specific workspace
|
|
91
|
+
const metadata = getCargoMetadata(configDir);
|
|
92
|
+
|
|
93
|
+
// 3. Skip if we've already processed this workspace (via another member)
|
|
94
|
+
if (processedWorkspaceRoots.has(metadata.workspace_root)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
processedWorkspaceRoots.add(metadata.workspace_root);
|
|
98
|
+
|
|
99
|
+
// 4. Process this workspace's internal dependencies
|
|
100
|
+
const workspaceDeps = processWorkspaceMetadata(ctx, metadata);
|
|
101
|
+
allDependencies.push(...workspaceDeps);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return allDependencies;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function processWorkspaceMetadata(
|
|
108
|
+
ctx: Context,
|
|
109
|
+
metadata: CargoMetadata
|
|
110
|
+
): GraphDependency[] {
|
|
111
|
+
const {
|
|
112
|
+
packages,
|
|
113
|
+
workspace_members: cargoWsMembers,
|
|
114
|
+
resolve: cargoResolve,
|
|
115
|
+
} = metadata;
|
|
116
|
+
|
|
117
|
+
const workspacePackages = new Map<CargoId, CargoPackage>();
|
|
118
|
+
for (const id of cargoWsMembers) {
|
|
119
|
+
const pkg = packages.find(p => p.id === id);
|
|
120
|
+
if (pkg) workspacePackages.set(id, pkg);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const nxData = mapCargoProjects(ctx, workspacePackages);
|
|
124
|
+
|
|
125
|
+
return cargoResolve.nodes
|
|
126
|
+
.filter(({ id }) => nxData.has(id))
|
|
127
|
+
.flatMap(({ id: sourceId, dependencies }) => {
|
|
128
|
+
const sourceProject = nxData.get(sourceId)!;
|
|
129
|
+
const cargoPackage = workspacePackages.get(sourceId)!;
|
|
130
|
+
const sourceManifest = path
|
|
131
|
+
.relative(ctx.workspaceRoot, cargoPackage.manifest_path)
|
|
132
|
+
.replace(/\\/g, "/");
|
|
133
|
+
|
|
134
|
+
return dependencies
|
|
135
|
+
.filter(depId => nxData.has(depId))
|
|
136
|
+
.map(depId => ({
|
|
137
|
+
source: sourceProject.name,
|
|
138
|
+
target: nxData.get(depId)!.name,
|
|
139
|
+
type: DependencyType.static,
|
|
140
|
+
sourceFile: sourceManifest,
|
|
141
|
+
}));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getCargoMetadata(cwd: string): CargoMetadata {
|
|
146
|
+
const availableMemory = os.freemem();
|
|
147
|
+
// Run cargo metadata from the specific directory of the Cargo.toml
|
|
148
|
+
const metadata = cp.execSync("cargo metadata --format-version=1", {
|
|
149
|
+
encoding: "utf8",
|
|
150
|
+
maxBuffer: availableMemory,
|
|
151
|
+
cwd: cwd, // Crucial: run in the workspace directory
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return JSON.parse(metadata);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
type WithReq<T, K extends keyof T> = Omit<T, K> & {
|
|
158
|
+
[Key in K]-?: Exclude<T[Key], null | undefined>;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
function mapCargoProjects(ctx: Context, packages: Map<CargoId, CargoPackage>) {
|
|
162
|
+
let result = new Map<CargoId, WithReq<ProjectConfiguration, "name">>();
|
|
163
|
+
|
|
164
|
+
for (let [cargoId, cargoPackage] of packages) {
|
|
165
|
+
if (!cargoPackage.manifest_path) {
|
|
166
|
+
throw new Error("Expected cargo package's `manifest_path` to exist");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let manifestDir = path.dirname(cargoPackage.manifest_path);
|
|
170
|
+
let projectDir = path
|
|
171
|
+
.relative(ctx.workspaceRoot, manifestDir)
|
|
172
|
+
.replace(/\\/g, "/");
|
|
173
|
+
|
|
174
|
+
let found = Object.entries(ctx.projects).find(
|
|
175
|
+
([, config]) => config.root === projectDir
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (found) {
|
|
179
|
+
let [projectName, projectConfig] = found;
|
|
180
|
+
|
|
181
|
+
result.set(cargoId, {
|
|
182
|
+
...projectConfig,
|
|
183
|
+
name: projectName,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result;
|
|
189
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./graph";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "es2022",
|
|
5
|
+
"outDir": "../../dist/out-tsc",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"noPropertyAccessFromIndexSignature": true
|
|
9
|
+
},
|
|
10
|
+
"exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"],
|
|
11
|
+
"include": ["**/*.ts"]
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"module": "es2022",
|
|
6
|
+
"types": ["jest", "node"]
|
|
7
|
+
},
|
|
8
|
+
"include": [
|
|
9
|
+
"**/*.spec.ts",
|
|
10
|
+
"**/*.test.ts",
|
|
11
|
+
"**/*.spec.tsx",
|
|
12
|
+
"**/*.test.tsx",
|
|
13
|
+
"**/*.spec.js",
|
|
14
|
+
"**/*.test.js",
|
|
15
|
+
"**/*.spec.jsx",
|
|
16
|
+
"**/*.test.jsx",
|
|
17
|
+
"**/*.d.ts",
|
|
18
|
+
"jest.config.ts"
|
|
19
|
+
]
|
|
20
|
+
}
|