@dbx-tools/projen 0.6.76 → 0.6.78
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/README.md +38 -0
- package/index.ts +8 -21
- package/package.json +4 -4
- package/src/pnpm-workspace.ts +1 -0
- package/src/project-js.ts +1246 -0
- package/src/project-predicate.ts +17 -6
- package/src/project-py.ts +401 -0
- package/src/project.ts +57 -1290
- package/src/publish.ts +2 -2
- package/src/vscode.ts +3 -3
package/src/project-predicate.ts
CHANGED
|
@@ -11,7 +11,8 @@ import { IConstruct } from "constructs";
|
|
|
11
11
|
import { Project } from "projen";
|
|
12
12
|
import { project } from "..";
|
|
13
13
|
import { toPosix } from "./packages.ts";
|
|
14
|
-
import { DBXToolsProject
|
|
14
|
+
import type { DBXToolsProject } from "./project.ts";
|
|
15
|
+
import type { DBXToolsJavaScriptProject } from "./project-js.ts";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Guard: the construct is a projen {@link Project} - the base every builder here
|
|
@@ -27,11 +28,19 @@ export function isProject(): Predicate<IConstruct, Project> {
|
|
|
27
28
|
return predicate.create((c: IConstruct): c is Project => Project.isProject(c));
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
/** Guard: the construct
|
|
31
|
+
/** Guard: the construct implements the language-agnostic {@link DBXToolsProject} contract. */
|
|
31
32
|
export function isDBXToolsProject(): Predicate<IConstruct, DBXToolsProject> {
|
|
32
33
|
return isProject().and(
|
|
33
34
|
(project): project is DBXToolsProject =>
|
|
34
|
-
project
|
|
35
|
+
(project as Partial<DBXToolsProject>).language === "javascript" ||
|
|
36
|
+
(project as Partial<DBXToolsProject>).language === "python",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Guard: the construct is a dbx-tools JavaScript/TypeScript project. */
|
|
41
|
+
export function isDBXToolsJavaScriptProject(): Predicate<IConstruct, DBXToolsJavaScriptProject> {
|
|
42
|
+
return isDBXToolsProject().and(
|
|
43
|
+
(project): project is DBXToolsJavaScriptProject => project.language === "javascript",
|
|
35
44
|
);
|
|
36
45
|
}
|
|
37
46
|
|
|
@@ -97,14 +106,16 @@ export function hasIdentifierScope(
|
|
|
97
106
|
|
|
98
107
|
/**
|
|
99
108
|
* Matches DBXTools packages carrying every listed tag (`dbxToolsConfig.tags`), narrowing
|
|
100
|
-
* {@link Project} to {@link
|
|
109
|
+
* {@link Project} to {@link DBXToolsJavaScriptProject} (tags live only on JavaScript packages). Also the
|
|
101
110
|
* guard backing each built-in {@link PACKAGE_TAG_MIXINS} entry. Keep it in the SAME `.and(...)`
|
|
102
111
|
* as any name/path filter (or last when chaining) - a later non-tag `.and` re-widens to
|
|
103
112
|
* {@link Project} and drops the narrowing.
|
|
104
113
|
*/
|
|
105
|
-
export function hasTag(
|
|
114
|
+
export function hasTag(
|
|
115
|
+
...tags: OneOrMany<PathMatchInput>
|
|
116
|
+
): Predicate<IConstruct, DBXToolsJavaScriptProject> {
|
|
106
117
|
const matchers = projectMatchers(...tags);
|
|
107
|
-
return
|
|
118
|
+
return isDBXToolsJavaScriptProject().and((project) =>
|
|
108
119
|
matchers.every((matcher) => project.dbxToolsConfig.tags.some((tag) => matcher(tag))),
|
|
109
120
|
);
|
|
110
121
|
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/** Reusable uv workspace generation for Python packages hosted in a projen tree. */
|
|
2
|
+
import { string } from "@dbx-tools/shared-core";
|
|
3
|
+
import { Component, type Project, javascript, python, vscode } from "projen";
|
|
4
|
+
import { GithubWorkflow } from "projen/lib/github";
|
|
5
|
+
import { JobPermission } from "projen/lib/github/workflows-model";
|
|
6
|
+
import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
|
|
7
|
+
|
|
8
|
+
/** Git location used by direct `#subdirectory=` package dependencies. */
|
|
9
|
+
export interface PythonRepositoryOptions {
|
|
10
|
+
readonly url: string;
|
|
11
|
+
readonly ref?: string;
|
|
12
|
+
readonly root?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** One independently installable Python package in the uv workspace. */
|
|
16
|
+
export interface PythonPackageOptions extends DBXToolsProjectOptions {
|
|
17
|
+
readonly directory: string;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly module: string;
|
|
20
|
+
readonly description: string;
|
|
21
|
+
readonly dependencies?: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Options for one projen-native Python workspace member. */
|
|
25
|
+
export interface DBXToolsPythonProjectOptions extends DBXToolsProjectOptions {
|
|
26
|
+
readonly parent: Project;
|
|
27
|
+
readonly package: PythonPackageOptions;
|
|
28
|
+
readonly repository: Required<PythonRepositoryOptions>;
|
|
29
|
+
readonly requiresPython: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Python release workflow configuration. */
|
|
33
|
+
export interface PythonReleaseOptions {
|
|
34
|
+
readonly workflowName?: string;
|
|
35
|
+
/** GitHub environment by Python distribution name. Defaults to `pypi-<name>`. */
|
|
36
|
+
readonly environments?: Readonly<Record<string, string>>;
|
|
37
|
+
readonly environmentUrl?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Options for {@link DBXToolsPythonWorkspace}. */
|
|
41
|
+
export interface DBXToolsPythonWorkspaceOptions {
|
|
42
|
+
readonly packages: readonly PythonPackageOptions[];
|
|
43
|
+
readonly repository: PythonRepositoryOptions;
|
|
44
|
+
readonly requiresPython?: string;
|
|
45
|
+
readonly ruffTarget?: string;
|
|
46
|
+
readonly workspaceName?: string;
|
|
47
|
+
readonly devDependencies?: readonly string[];
|
|
48
|
+
readonly testPaths?: readonly string[];
|
|
49
|
+
readonly lintPaths?: readonly string[];
|
|
50
|
+
readonly ruffPerFileIgnores?: Readonly<Record<string, readonly string[]>>;
|
|
51
|
+
readonly interpreterPath?: string | false;
|
|
52
|
+
readonly release?: boolean | PythonReleaseOptions;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DEFAULT_DEV_DEPENDENCIES = [
|
|
56
|
+
"pytest>=8.4,<9",
|
|
57
|
+
"pytest-asyncio>=1.1,<2",
|
|
58
|
+
"pyyaml>=6.0,<7",
|
|
59
|
+
"ruff>=0.12,<1",
|
|
60
|
+
] as const;
|
|
61
|
+
|
|
62
|
+
const quote = (value: string): string => JSON.stringify(value);
|
|
63
|
+
|
|
64
|
+
/** Repository-relative path for a Python package directory. */
|
|
65
|
+
export function pythonPackagePath(repository: PythonRepositoryOptions, directory: string): string {
|
|
66
|
+
return `${repository.root ?? "packages/py"}/${directory}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** PEP 508 dependency pointing at a sibling package in a Git repository. */
|
|
70
|
+
export function pythonGitDependency(
|
|
71
|
+
repository: PythonRepositoryOptions,
|
|
72
|
+
name: string,
|
|
73
|
+
directory: string,
|
|
74
|
+
): string {
|
|
75
|
+
return `${name} @ git+${repository.url}@${repository.ref ?? "main"}#subdirectory=${pythonPackagePath(repository, directory)}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function projectVscode(project: Project): vscode.VsCode | undefined {
|
|
79
|
+
return (project as Project & { readonly vscode?: vscode.VsCode }).vscode;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A Python package implemented with projen's `PythonProject` and uv backend. */
|
|
83
|
+
export class DBXToolsPythonProject extends python.PythonProject implements DBXToolsProject {
|
|
84
|
+
readonly language = "python" as const;
|
|
85
|
+
readonly packageOptions: PythonPackageOptions;
|
|
86
|
+
readonly uv: python.Uv;
|
|
87
|
+
|
|
88
|
+
constructor(options: DBXToolsPythonProjectOptions) {
|
|
89
|
+
const pkg = options.package;
|
|
90
|
+
super({
|
|
91
|
+
parent: options.parent,
|
|
92
|
+
outdir: pythonPackagePath(options.repository, pkg.directory),
|
|
93
|
+
name: pkg.name,
|
|
94
|
+
moduleName: pkg.module,
|
|
95
|
+
authorName: "",
|
|
96
|
+
authorEmail: "",
|
|
97
|
+
version: "0.0.0",
|
|
98
|
+
description: pkg.description,
|
|
99
|
+
github: false,
|
|
100
|
+
sample: false,
|
|
101
|
+
pytest: false,
|
|
102
|
+
projenrcPython: false,
|
|
103
|
+
projenrcJs: false,
|
|
104
|
+
projenrcTs: false,
|
|
105
|
+
pip: false,
|
|
106
|
+
venv: false,
|
|
107
|
+
setuptools: false,
|
|
108
|
+
poetry: false,
|
|
109
|
+
uv: true,
|
|
110
|
+
projenCommand: options.parent.projenCommand,
|
|
111
|
+
uvOptions: {
|
|
112
|
+
project: {
|
|
113
|
+
name: pkg.name,
|
|
114
|
+
version: "0.0.0",
|
|
115
|
+
description: pkg.description,
|
|
116
|
+
readme: "README.md",
|
|
117
|
+
requiresPython: options.requiresPython,
|
|
118
|
+
dependencies: [...(pkg.dependencies ?? [])],
|
|
119
|
+
urls: {
|
|
120
|
+
Source: `${options.repository.url.replace(/\.git$/, "")}/tree/${options.repository.ref}/${pythonPackagePath(options.repository, pkg.directory)}`,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
buildSystem: {
|
|
124
|
+
requires: ["uv_build>=0.11.28,<0.12.0"],
|
|
125
|
+
buildBackend: "uv_build",
|
|
126
|
+
},
|
|
127
|
+
uv: {
|
|
128
|
+
buildBackend: {
|
|
129
|
+
moduleName: pkg.module,
|
|
130
|
+
moduleRoot: "src",
|
|
131
|
+
namespace: true,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
this.packageOptions = pkg;
|
|
137
|
+
if (!(this.packagingManager instanceof python.Uv)) {
|
|
138
|
+
throw new Error(`Expected uv packaging for ${pkg.name}`);
|
|
139
|
+
}
|
|
140
|
+
this.uv = this.packagingManager;
|
|
141
|
+
this.uv.file.addDeletionOverride("project.authors");
|
|
142
|
+
this.uv.file.addDeletionOverride("dependency-groups");
|
|
143
|
+
this.uv.file.readonly = true;
|
|
144
|
+
|
|
145
|
+
for (const path of [".gitattributes", ".gitignore"]) {
|
|
146
|
+
this.tryRemoveFile(path);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The root workspace owns dependency installation for every member. */
|
|
151
|
+
public override postSynthesize(): void {}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Generates a root uv workspace, projen-native Python member projects, Python
|
|
156
|
+
* tasks, editor interpreter selection, and an optional publishing workflow.
|
|
157
|
+
*/
|
|
158
|
+
export class DBXToolsPythonWorkspace extends Component {
|
|
159
|
+
readonly packages: readonly DBXToolsPythonProject[];
|
|
160
|
+
readonly repository: Required<PythonRepositoryOptions>;
|
|
161
|
+
readonly requiresPython: string;
|
|
162
|
+
readonly file: python.PyprojectTomlFile;
|
|
163
|
+
|
|
164
|
+
constructor(project: javascript.NodeProject, options: DBXToolsPythonWorkspaceOptions) {
|
|
165
|
+
super(project);
|
|
166
|
+
this.repository = {
|
|
167
|
+
url: options.repository.url,
|
|
168
|
+
ref: options.repository.ref ?? "main",
|
|
169
|
+
root: options.repository.root ?? "packages/py",
|
|
170
|
+
};
|
|
171
|
+
this.requiresPython = options.requiresPython ?? ">=3.10";
|
|
172
|
+
this.file = this.emitWorkspace(project, options);
|
|
173
|
+
this.packages = options.packages.map(
|
|
174
|
+
(pkg) =>
|
|
175
|
+
new DBXToolsPythonProject({
|
|
176
|
+
parent: project,
|
|
177
|
+
package: pkg,
|
|
178
|
+
repository: this.repository,
|
|
179
|
+
requiresPython: this.requiresPython,
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
for (const pkg of this.packages) {
|
|
183
|
+
const pyproject = `/${pythonPackagePath(this.repository, pkg.packageOptions.directory)}/pyproject.toml`;
|
|
184
|
+
project.gitignore.include(pyproject);
|
|
185
|
+
project.gitattributes.addAttributes(pyproject, "linguist-generated");
|
|
186
|
+
project.prettier?.addIgnorePattern(pyproject.slice(1));
|
|
187
|
+
}
|
|
188
|
+
this.addTasks(project, options);
|
|
189
|
+
|
|
190
|
+
const interpreterPath = options.interpreterPath ?? "${workspaceFolder}/.venv/bin/python";
|
|
191
|
+
if (interpreterPath !== false) {
|
|
192
|
+
projectVscode(project)?.settings.addSetting("python.defaultInterpreterPath", interpreterPath);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (options.release) {
|
|
196
|
+
this.addReleaseWorkflow(project, options.release === true ? {} : options.release);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Repository-relative package directory. */
|
|
201
|
+
packagePath(directory: string): string {
|
|
202
|
+
return pythonPackagePath(this.repository, directory);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** PEP 508 dependency pointing at a sibling package in the configured repository. */
|
|
206
|
+
gitDependency(name: string, directory: string): string {
|
|
207
|
+
return pythonGitDependency(this.repository, name, directory);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private emitWorkspace(
|
|
211
|
+
project: javascript.NodeProject,
|
|
212
|
+
options: DBXToolsPythonWorkspaceOptions,
|
|
213
|
+
): python.PyprojectTomlFile {
|
|
214
|
+
const testPaths = options.testPaths ?? [this.repository.root];
|
|
215
|
+
const perFileIgnores = options.ruffPerFileIgnores ?? {};
|
|
216
|
+
const file = new python.PyprojectTomlFile(project, {
|
|
217
|
+
project: {
|
|
218
|
+
name: options.workspaceName ?? `${string.toSlug(project.name)}-python-workspace`,
|
|
219
|
+
version: "0.0.0",
|
|
220
|
+
requiresPython: this.requiresPython,
|
|
221
|
+
dependencies: [],
|
|
222
|
+
},
|
|
223
|
+
dependencyGroups: {
|
|
224
|
+
dev: [...(options.devDependencies ?? DEFAULT_DEV_DEPENDENCIES)],
|
|
225
|
+
},
|
|
226
|
+
tool: {
|
|
227
|
+
uv: python.uvConfig.toJson_UvConfiguration({
|
|
228
|
+
package: false,
|
|
229
|
+
workspace: { members: [`${this.repository.root}/*`] },
|
|
230
|
+
}),
|
|
231
|
+
pytest: {
|
|
232
|
+
ini_options: {
|
|
233
|
+
asyncio_mode: "auto",
|
|
234
|
+
testpaths: testPaths,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
ruff: {
|
|
238
|
+
"target-version": options.ruffTarget ?? "py310",
|
|
239
|
+
"line-length": 100,
|
|
240
|
+
lint: {
|
|
241
|
+
"per-file-ignores": perFileIgnores,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
file.addOverride(
|
|
247
|
+
"tool.uv.sources",
|
|
248
|
+
Object.fromEntries(options.packages.map((pkg) => [pkg.name, { workspace: true }])),
|
|
249
|
+
);
|
|
250
|
+
file.readonly = true;
|
|
251
|
+
return file;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private addTasks(project: javascript.NodeProject, options: DBXToolsPythonWorkspaceOptions): void {
|
|
255
|
+
const lintPaths = options.lintPaths ?? [this.repository.root];
|
|
256
|
+
project.addTask("py:sync", {
|
|
257
|
+
exec: "uv sync --all-packages",
|
|
258
|
+
description: "Resolve and install every Python workspace package",
|
|
259
|
+
});
|
|
260
|
+
project.addTask("py:test", {
|
|
261
|
+
exec: "uv run pytest",
|
|
262
|
+
description: "Run Python workspace tests",
|
|
263
|
+
});
|
|
264
|
+
project.addTask("py:lint", {
|
|
265
|
+
exec: `uv run ruff check ${lintPaths.join(" ")}`,
|
|
266
|
+
description: "Lint Python workspace packages",
|
|
267
|
+
});
|
|
268
|
+
project.addTask("py:format", {
|
|
269
|
+
exec: `uv run ruff format ${lintPaths.join(" ")}`,
|
|
270
|
+
description: "Format Python workspace packages",
|
|
271
|
+
});
|
|
272
|
+
project.addTask("py:build", {
|
|
273
|
+
exec: "uv build --all-packages",
|
|
274
|
+
description: "Build every Python workspace package",
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private addReleaseWorkflow(project: javascript.NodeProject, options: PythonReleaseOptions): void {
|
|
279
|
+
if (!project.github) return;
|
|
280
|
+
const workflow = new GithubWorkflow(project.github, options.workflowName ?? "python-release");
|
|
281
|
+
workflow.file?.addOverride("permissions", { contents: "read" });
|
|
282
|
+
workflow.on({ workflowDispatch: {} });
|
|
283
|
+
workflow.file?.addOverride("on.workflow_dispatch", {
|
|
284
|
+
inputs: {
|
|
285
|
+
version: {
|
|
286
|
+
description: "Python package version to build",
|
|
287
|
+
type: "string",
|
|
288
|
+
required: true,
|
|
289
|
+
},
|
|
290
|
+
publish: {
|
|
291
|
+
description: "Upload the validated distributions to PyPI",
|
|
292
|
+
type: "boolean",
|
|
293
|
+
default: false,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
workflow.addJob("build", {
|
|
298
|
+
runsOn: ["ubuntu-latest"],
|
|
299
|
+
permissions: { contents: JobPermission.READ },
|
|
300
|
+
timeoutMinutes: 20,
|
|
301
|
+
steps: [
|
|
302
|
+
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
303
|
+
{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
|
|
304
|
+
{
|
|
305
|
+
name: "Stamp workspace versions",
|
|
306
|
+
env: { VERSION: "${{ inputs.version }}" },
|
|
307
|
+
run: this.renderVersionStampScript(),
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "Build distributions",
|
|
311
|
+
run: this.packages
|
|
312
|
+
.map(
|
|
313
|
+
(pkg) =>
|
|
314
|
+
`uv build --package ${pkg.packageOptions.name} --out-dir dist/${pkg.packageOptions.directory}`,
|
|
315
|
+
)
|
|
316
|
+
.join("\n"),
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "Validate distributions",
|
|
320
|
+
run: [
|
|
321
|
+
`test "$(find dist -type f \\( -name '*.whl' -o -name '*.tar.gz' \\) | wc -l | tr -d ' ')" -eq ${this.packages.length * 2}`,
|
|
322
|
+
"uvx twine check dist/*/*.whl dist/*/*.tar.gz",
|
|
323
|
+
].join("\n"),
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "Upload distributions",
|
|
327
|
+
uses: "actions/upload-artifact@v7",
|
|
328
|
+
with: { name: "python-distributions", path: "dist" },
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
});
|
|
332
|
+
for (const pkg of this.packages) {
|
|
333
|
+
workflow.addJob(`publish-${pkg.packageOptions.directory}`, {
|
|
334
|
+
if: "${{ inputs.publish }}",
|
|
335
|
+
needs: ["build"],
|
|
336
|
+
environment: {
|
|
337
|
+
name:
|
|
338
|
+
options.environments?.[pkg.packageOptions.name] ?? `pypi-${pkg.packageOptions.name}`,
|
|
339
|
+
url:
|
|
340
|
+
options.environmentUrl ??
|
|
341
|
+
`https://pypi.org/project/${pkg.packageOptions.name.replaceAll("_", "-")}/`,
|
|
342
|
+
},
|
|
343
|
+
runsOn: ["ubuntu-latest"],
|
|
344
|
+
permissions: { idToken: JobPermission.WRITE },
|
|
345
|
+
timeoutMinutes: 10,
|
|
346
|
+
steps: [
|
|
347
|
+
{
|
|
348
|
+
name: "Download distributions",
|
|
349
|
+
uses: "actions/download-artifact@v8",
|
|
350
|
+
with: { name: "python-distributions", path: "dist" },
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: `Publish ${pkg.packageOptions.name} to PyPI`,
|
|
354
|
+
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
355
|
+
with: { "packages-dir": `dist/${pkg.packageOptions.directory}` },
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private renderVersionStampScript(): string {
|
|
363
|
+
return [
|
|
364
|
+
`chmod -R u+w ${this.repository.root}`,
|
|
365
|
+
"python - <<'PY'",
|
|
366
|
+
"from pathlib import Path",
|
|
367
|
+
"import os",
|
|
368
|
+
"import re",
|
|
369
|
+
"",
|
|
370
|
+
'version = os.environ["VERSION"]',
|
|
371
|
+
`package_files = sorted(Path(${quote(this.repository.root)}).glob("*/pyproject.toml"))`,
|
|
372
|
+
"packages = {}",
|
|
373
|
+
"for path in package_files:",
|
|
374
|
+
' source = path.read_text(encoding="utf-8")',
|
|
375
|
+
' name = re.search(r\'^name = "([^"]+)"$\', source, re.MULTILINE)',
|
|
376
|
+
" if name is None:",
|
|
377
|
+
' raise ValueError(f"Missing project name in {path}")',
|
|
378
|
+
" packages[name.group(1)] = path.parent.name",
|
|
379
|
+
"",
|
|
380
|
+
"for path in package_files:",
|
|
381
|
+
' source = path.read_text(encoding="utf-8")',
|
|
382
|
+
" source, count = re.subn(",
|
|
383
|
+
' r\'^version = "[^"]+"$\',',
|
|
384
|
+
" f'version = \"{version}\"',",
|
|
385
|
+
" source,",
|
|
386
|
+
" count=1,",
|
|
387
|
+
" flags=re.MULTILINE,",
|
|
388
|
+
" )",
|
|
389
|
+
" if count != 1:",
|
|
390
|
+
' raise ValueError(f"Expected one project version in {path}")',
|
|
391
|
+
" for name, directory in packages.items():",
|
|
392
|
+
" source = re.sub(",
|
|
393
|
+
` rf'{re.escape(name)} @ git\\+[^" ]+#subdirectory=${this.repository.root}/{re.escape(directory)}',`,
|
|
394
|
+
' f"{name}=={version}",',
|
|
395
|
+
" source,",
|
|
396
|
+
" )",
|
|
397
|
+
' path.write_text(source, encoding="utf-8")',
|
|
398
|
+
"PY",
|
|
399
|
+
].join("\n");
|
|
400
|
+
}
|
|
401
|
+
}
|