@typespec/http-client-python 0.28.3 → 0.29.0
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/dist/emitter/code-model.d.ts.map +1 -1
- package/dist/emitter/code-model.js +1 -0
- package/dist/emitter/code-model.js.map +1 -1
- package/dist/emitter/constants.d.ts +6 -0
- package/dist/emitter/constants.d.ts.map +1 -0
- package/dist/emitter/constants.js +27 -0
- package/dist/emitter/constants.js.map +1 -0
- package/dist/emitter/emitter.d.ts.map +1 -1
- package/dist/emitter/emitter.js +169 -94
- package/dist/emitter/emitter.js.map +1 -1
- package/dist/emitter/http.d.ts.map +1 -1
- package/dist/emitter/http.js +33 -1
- package/dist/emitter/http.js.map +1 -1
- package/dist/emitter/lib.d.ts +33 -2
- package/dist/emitter/lib.d.ts.map +1 -1
- package/dist/emitter/lib.js +17 -0
- package/dist/emitter/lib.js.map +1 -1
- package/dist/emitter/run-python3.js +1 -1
- package/dist/emitter/run-python3.js.map +1 -1
- package/dist/emitter/types.d.ts.map +1 -1
- package/dist/emitter/types.js +31 -5
- package/dist/emitter/types.js.map +1 -1
- package/emitter/src/code-model.ts +1 -0
- package/emitter/src/constants.ts +27 -0
- package/emitter/src/emitter.ts +216 -96
- package/emitter/src/http.ts +29 -1
- package/emitter/src/lib.ts +20 -0
- package/emitter/src/run-python3.ts +1 -1
- package/emitter/src/types.ts +35 -5
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/emitter/test/utils.test.ts +3 -0
- package/emitter/tsconfig.build.json +2 -1
- package/emitter/tsconfig.json +2 -1
- package/eng/scripts/ci/config/eslint-ci.config.mjs +10 -0
- package/eng/scripts/ci/config/mypy.ini +1 -1
- package/eng/scripts/ci/config/pylintrc +1 -1
- package/eng/scripts/ci/config/pyrightconfig.json +1 -1
- package/eng/scripts/ci/dev_requirements.txt +1 -1
- package/eng/scripts/ci/lint.ts +11 -4
- package/eng/scripts/ci/regenerate-common.ts +407 -136
- package/eng/scripts/ci/regenerate.ts +103 -194
- package/eng/scripts/ci/run-tests.ts +153 -48
- package/eng/scripts/ci/run_apiview.py +22 -16
- package/eng/scripts/ci/run_mypy.py +28 -18
- package/eng/scripts/ci/run_pylint.py +23 -16
- package/eng/scripts/ci/run_pyright.py +5 -2
- package/eng/scripts/ci/run_sphinx_build.py +24 -9
- package/eng/scripts/ci/util.py +61 -25
- package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
- package/eng/scripts/setup/build_pygen_wheel.py +3 -3
- package/eng/scripts/setup/install.py +3 -3
- package/eng/scripts/setup/install.ts +1 -1
- package/eng/scripts/setup/prepare.py +2 -2
- package/eng/scripts/setup/run-python3.ts +1 -1
- package/eng/scripts/setup/run_batch.py +197 -0
- package/eng/scripts/setup/venvtools.py +0 -1
- package/generator/build/lib/pygen/__init__.py +1 -3
- package/generator/build/lib/pygen/codegen/__init__.py +0 -1
- package/generator/build/lib/pygen/codegen/models/base.py +0 -1
- package/generator/build/lib/pygen/codegen/models/client.py +2 -1
- package/generator/build/lib/pygen/codegen/models/code_model.py +1 -0
- package/generator/build/lib/pygen/codegen/models/enum_type.py +0 -1
- package/generator/build/lib/pygen/codegen/models/model_type.py +1 -7
- package/generator/build/lib/pygen/codegen/models/parameter.py +16 -14
- package/generator/build/lib/pygen/codegen/serializers/__init__.py +27 -12
- package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +12 -3
- package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +6 -2
- package/generator/build/lib/pygen/codegen/serializers/import_serializer.py +17 -4
- package/generator/build/lib/pygen/codegen/serializers/parameter_serializer.py +17 -11
- package/generator/build/lib/pygen/codegen/templates/model_base.py.jinja2 +19 -7
- package/generator/build/lib/pygen/codegen/templates/packaging_templates/README.md.jinja2 +11 -3
- package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
- package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +5 -1
- package/generator/build/lib/pygen/preprocess/__init__.py +37 -30
- package/generator/component-detection-pip-report.json +7 -7
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/__init__.py +1 -3
- package/generator/pygen/codegen/__init__.py +0 -1
- package/generator/pygen/codegen/models/base.py +0 -1
- package/generator/pygen/codegen/models/client.py +2 -1
- package/generator/pygen/codegen/models/code_model.py +1 -0
- package/generator/pygen/codegen/models/enum_type.py +0 -1
- package/generator/pygen/codegen/models/model_type.py +1 -7
- package/generator/pygen/codegen/models/parameter.py +16 -14
- package/generator/pygen/codegen/serializers/__init__.py +27 -12
- package/generator/pygen/codegen/serializers/builder_serializer.py +12 -3
- package/generator/pygen/codegen/serializers/general_serializer.py +6 -2
- package/generator/pygen/codegen/serializers/import_serializer.py +17 -4
- package/generator/pygen/codegen/serializers/parameter_serializer.py +17 -11
- package/generator/pygen/codegen/templates/model_base.py.jinja2 +19 -7
- package/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 +11 -3
- package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
- package/generator/pygen/codegen/templates/serialization.py.jinja2 +5 -1
- package/generator/pygen/preprocess/__init__.py +37 -30
- package/generator/pygen.egg-info/PKG-INFO +1 -2
- package/generator/pygen.egg-info/requires.txt +1 -1
- package/generator/setup.py +1 -2
- package/package.json +24 -9
|
@@ -1,9 +1,80 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/**
|
|
3
|
+
* Shared helpers, types, constants, and data tables used by `regenerate.ts`.
|
|
4
|
+
*
|
|
5
|
+
* This file is meant to be **byte-identical** between this package and the
|
|
6
|
+
* upstream `@typespec/http-client-python`. typespec-python syncs it from
|
|
7
|
+
* <repo-root>/core/packages/http-client-python/eng/scripts/ci/regenerate-common.ts
|
|
8
|
+
* via `pnpm sync`.
|
|
9
|
+
*
|
|
10
|
+
* Per-repo divergence (paths, emitter name, single-phase vs two-phase
|
|
11
|
+
* orchestration, argv/help text) lives in each repo's own `regenerate.ts`,
|
|
12
|
+
* which builds a `RegenerateContext` and feeds it into the helpers exported
|
|
13
|
+
* from this module.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { compile, NodeHost } from "@typespec/compiler";
|
|
17
|
+
import { execSync } from "child_process";
|
|
18
|
+
import { existsSync, rmSync } from "fs";
|
|
19
|
+
import { access, cp, mkdir, mkdtemp, readdir, writeFile } from "fs/promises";
|
|
20
|
+
import { tmpdir } from "os";
|
|
2
21
|
import { dirname, join, relative, resolve } from "path";
|
|
22
|
+
import pc from "picocolors";
|
|
3
23
|
|
|
4
|
-
// ----
|
|
24
|
+
// ---- Public types ----
|
|
5
25
|
|
|
6
|
-
export
|
|
26
|
+
export interface RegenerateFlags {
|
|
27
|
+
flavor: string;
|
|
28
|
+
debug: boolean;
|
|
29
|
+
name?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CompileTask {
|
|
33
|
+
spec: string;
|
|
34
|
+
outputDir: string;
|
|
35
|
+
options: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Group of tasks for the same spec that must run sequentially
|
|
39
|
+
export interface TaskGroup {
|
|
40
|
+
spec: string;
|
|
41
|
+
tasks: CompileTask[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Per-repo context injected into the helpers below. Every value is repo
|
|
46
|
+
* specific and must be supplied by the caller's `regenerate.ts`.
|
|
47
|
+
*/
|
|
48
|
+
export interface RegenerateContext {
|
|
49
|
+
/** Absolute path to the package root (the dir containing `package.json`). */
|
|
50
|
+
pluginDir: string;
|
|
51
|
+
/** Absolute path to the azure-http-specs `specs/` dir. */
|
|
52
|
+
azureHttpSpecs: string;
|
|
53
|
+
/** Absolute path to the http-specs `specs/` dir. */
|
|
54
|
+
httpSpecs: string;
|
|
55
|
+
/** Absolute path to where generated SDKs should go (e.g. `<plugin>/generator`). */
|
|
56
|
+
generatedFolder: string;
|
|
57
|
+
/** Emitter name to invoke (e.g. `@azure-tools/typespec-python`). */
|
|
58
|
+
emitterName: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Optional knobs for `buildTaskGroups`. Kept here so the call site in each
|
|
63
|
+
* repo's `regenerate.ts` can opt into the upstream two-phase pipeline
|
|
64
|
+
* (`emitYamlOnly: true`) or the single-phase pipeline (default).
|
|
65
|
+
*/
|
|
66
|
+
export interface BuildTaskGroupsOptions {
|
|
67
|
+
/** If true, ask the emitter to write YAML only and skip Python codegen. */
|
|
68
|
+
emitYamlOnly?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---- Public constants ----
|
|
72
|
+
|
|
73
|
+
export const SKIP_SPECS: string[] = [
|
|
74
|
+
"type/file",
|
|
75
|
+
"service/multiple-services",
|
|
76
|
+
"azure/client-generator-core/response-as-bool",
|
|
77
|
+
];
|
|
7
78
|
|
|
8
79
|
export const SpecialFlags: Record<string, Record<string, any>> = {
|
|
9
80
|
azure: {
|
|
@@ -12,9 +83,9 @@ export const SpecialFlags: Record<string, Record<string, any>> = {
|
|
|
12
83
|
},
|
|
13
84
|
};
|
|
14
85
|
|
|
15
|
-
// ----
|
|
86
|
+
// ---- Spec-specific emitter option overrides ----
|
|
16
87
|
|
|
17
|
-
export const
|
|
88
|
+
export const AZURE_EMITTER_OPTIONS: Record<
|
|
18
89
|
string,
|
|
19
90
|
Record<string, string> | Record<string, string>[]
|
|
20
91
|
> = {
|
|
@@ -48,6 +119,9 @@ export const BASE_AZURE_EMITTER_OPTIONS: Record<
|
|
|
48
119
|
"azure/client-generator-core/usage": {
|
|
49
120
|
namespace: "specs.azure.clientgenerator.core.usage",
|
|
50
121
|
},
|
|
122
|
+
"azure/client-generator-core/client-doc": {
|
|
123
|
+
namespace: "specs.azure.clientgenerator.core.clientdoc",
|
|
124
|
+
},
|
|
51
125
|
"azure/client-generator-core/override": {
|
|
52
126
|
namespace: "specs.azure.clientgenerator.core.override",
|
|
53
127
|
},
|
|
@@ -135,12 +209,13 @@ export const BASE_AZURE_EMITTER_OPTIONS: Record<
|
|
|
135
209
|
"service/multi-service": {
|
|
136
210
|
namespace: "service.multiservice",
|
|
137
211
|
},
|
|
212
|
+
"client/structure/client-operation-group": {
|
|
213
|
+
"package-name": "client-structure-clientoperationgroup",
|
|
214
|
+
namespace: "client.structure.clientoperationgroup",
|
|
215
|
+
},
|
|
138
216
|
};
|
|
139
217
|
|
|
140
|
-
export const
|
|
141
|
-
string,
|
|
142
|
-
Record<string, string> | Record<string, string>[]
|
|
143
|
-
> = {
|
|
218
|
+
export const EMITTER_OPTIONS: Record<string, Record<string, string> | Record<string, string>[]> = {
|
|
144
219
|
"resiliency/srv-driven/old.tsp": {
|
|
145
220
|
"package-name": "resiliency-srv-driven1",
|
|
146
221
|
namespace: "resiliency.srv.driven1",
|
|
@@ -208,10 +283,19 @@ export const BASE_EMITTER_OPTIONS: Record<
|
|
|
208
283
|
"package-name": "typetest-model-singlediscriminator",
|
|
209
284
|
namespace: "typetest.model.singlediscriminator",
|
|
210
285
|
},
|
|
211
|
-
"type/model/inheritance/recursive":
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
286
|
+
"type/model/inheritance/recursive": [
|
|
287
|
+
{
|
|
288
|
+
"package-name": "typetest-model-recursive",
|
|
289
|
+
namespace: "typetest.model.recursive",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"package-name": "generation-subdir",
|
|
293
|
+
namespace: "generation.subdir",
|
|
294
|
+
"generation-subdir": "_generated",
|
|
295
|
+
"generate-test": "false",
|
|
296
|
+
"clear-output-folder": "true",
|
|
297
|
+
},
|
|
298
|
+
],
|
|
215
299
|
"type/model/usage": {
|
|
216
300
|
"package-name": "typetest-model-usage",
|
|
217
301
|
namespace: "typetest.model.usage",
|
|
@@ -268,66 +352,63 @@ export const BASE_EMITTER_OPTIONS: Record<
|
|
|
268
352
|
"package-name": "specs-documentation",
|
|
269
353
|
namespace: "specs.documentation",
|
|
270
354
|
},
|
|
355
|
+
"versioning/added": [
|
|
356
|
+
{
|
|
357
|
+
"package-name": "versioning-added",
|
|
358
|
+
namespace: "versioning.added",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
"package-name": "generation-subdir2",
|
|
362
|
+
namespace: "generation.subdir2",
|
|
363
|
+
"generate-test": "false",
|
|
364
|
+
"generation-subdir": "_generated",
|
|
365
|
+
},
|
|
366
|
+
],
|
|
271
367
|
};
|
|
272
368
|
|
|
273
|
-
// ----
|
|
274
|
-
|
|
275
|
-
export interface TspCommand {
|
|
276
|
-
outputDir: string;
|
|
277
|
-
command: string | string[];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
export interface RegenerateFlagsInput {
|
|
281
|
-
flavor?: string;
|
|
282
|
-
debug?: boolean;
|
|
283
|
-
name?: string;
|
|
284
|
-
pyodide?: boolean;
|
|
285
|
-
jobs?: number;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
export interface RegenerateFlags {
|
|
289
|
-
flavor: string;
|
|
290
|
-
debug: boolean;
|
|
291
|
-
name?: string;
|
|
292
|
-
pyodide?: boolean;
|
|
293
|
-
}
|
|
369
|
+
// ---- Public helpers ----
|
|
294
370
|
|
|
295
|
-
export
|
|
296
|
-
|
|
297
|
-
outputDir: string;
|
|
371
|
+
export function toPosix(p: string): string {
|
|
372
|
+
return p.replace(/\\/g, "/");
|
|
298
373
|
}
|
|
299
374
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
375
|
+
/**
|
|
376
|
+
* Whether a spec path belongs to azure-http-specs (vs standard http-specs).
|
|
377
|
+
* Uses the `azure-http-specs` substring rather than `azure` to avoid false
|
|
378
|
+
* positives when the working-dir path itself contains "azure" (e.g.
|
|
379
|
+
* azure-sdk-for-python).
|
|
380
|
+
*/
|
|
381
|
+
export function isAzureSpec(spec: string): boolean {
|
|
382
|
+
return spec.includes("azure-http-specs");
|
|
308
383
|
}
|
|
309
384
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
385
|
+
export function defaultPackageName(spec: string, ctx: RegenerateContext): string {
|
|
386
|
+
const specDir = isAzureSpec(spec) ? ctx.azureHttpSpecs : ctx.httpSpecs;
|
|
387
|
+
return toPosix(relative(specDir, dirname(spec)))
|
|
388
|
+
.replace(/\//g, "-")
|
|
389
|
+
.toLowerCase();
|
|
314
390
|
}
|
|
315
391
|
|
|
316
|
-
export function
|
|
392
|
+
export function getEmitterOptions(
|
|
317
393
|
spec: string,
|
|
318
394
|
flavor: string,
|
|
319
|
-
|
|
395
|
+
ctx: RegenerateContext,
|
|
320
396
|
): Record<string, string>[] {
|
|
321
|
-
const specDir = spec
|
|
397
|
+
const specDir = isAzureSpec(spec) ? ctx.azureHttpSpecs : ctx.httpSpecs;
|
|
322
398
|
const relativeSpec = toPosix(relative(specDir, spec));
|
|
323
399
|
const key = relativeSpec.includes("resiliency/srv-driven/old.tsp")
|
|
324
400
|
? relativeSpec
|
|
325
401
|
: dirname(relativeSpec);
|
|
326
|
-
const
|
|
327
|
-
(flavor === "azure" ?
|
|
328
|
-
return Array.isArray(
|
|
402
|
+
const emitterOpts = EMITTER_OPTIONS[key] ||
|
|
403
|
+
(flavor === "azure" ? AZURE_EMITTER_OPTIONS[key] : [{}]) || [{}];
|
|
404
|
+
return Array.isArray(emitterOpts) ? emitterOpts : [emitterOpts];
|
|
329
405
|
}
|
|
330
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Walk `baseDir` and collect every TypeSpec entry-point file that the
|
|
409
|
+
* regenerator should compile (handles `client.tsp`, `main.tsp`, and the
|
|
410
|
+
* special `resiliency/srv-driven/old.tsp` case).
|
|
411
|
+
*/
|
|
331
412
|
export async function getSubdirectories(
|
|
332
413
|
baseDir: string,
|
|
333
414
|
flags: RegenerateFlags,
|
|
@@ -335,7 +416,7 @@ export async function getSubdirectories(
|
|
|
335
416
|
const subdirectories: string[] = [];
|
|
336
417
|
|
|
337
418
|
async function searchDir(currentDir: string) {
|
|
338
|
-
const items = await
|
|
419
|
+
const items = await readdir(currentDir, { withFileTypes: true });
|
|
339
420
|
|
|
340
421
|
const promisesArray = items.map(async (item) => {
|
|
341
422
|
const subDirPath = join(currentDir, item.name);
|
|
@@ -347,12 +428,10 @@ export async function getSubdirectories(
|
|
|
347
428
|
|
|
348
429
|
if (SKIP_SPECS.some((skipSpec) => mainTspRelativePath.includes(skipSpec))) return;
|
|
349
430
|
|
|
350
|
-
const hasMainTsp = await
|
|
351
|
-
.access(mainTspPath)
|
|
431
|
+
const hasMainTsp = await access(mainTspPath)
|
|
352
432
|
.then(() => true)
|
|
353
433
|
.catch(() => false);
|
|
354
|
-
const hasClientTsp = await
|
|
355
|
-
.access(clientTspPath)
|
|
434
|
+
const hasClientTsp = await access(clientTspPath)
|
|
356
435
|
.then(() => true)
|
|
357
436
|
.catch(() => false);
|
|
358
437
|
|
|
@@ -367,7 +446,6 @@ export async function getSubdirectories(
|
|
|
367
446
|
}
|
|
368
447
|
}
|
|
369
448
|
|
|
370
|
-
// Recursively search in the subdirectory
|
|
371
449
|
await searchDir(subDirPath);
|
|
372
450
|
}
|
|
373
451
|
});
|
|
@@ -379,98 +457,291 @@ export async function getSubdirectories(
|
|
|
379
457
|
return subdirectories;
|
|
380
458
|
}
|
|
381
459
|
|
|
382
|
-
export function
|
|
383
|
-
|
|
384
|
-
return toPosix(relative(specDir, dirname(spec)))
|
|
385
|
-
.replace(/\//g, "-")
|
|
386
|
-
.toLowerCase();
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export function buildOptions(
|
|
390
|
-
spec: string,
|
|
391
|
-
generatedFolder: string,
|
|
460
|
+
export function buildTaskGroups(
|
|
461
|
+
specs: string[],
|
|
392
462
|
flags: RegenerateFlags,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
463
|
+
ctx: RegenerateContext,
|
|
464
|
+
options: BuildTaskGroupsOptions = {},
|
|
465
|
+
): TaskGroup[] {
|
|
466
|
+
const groups: TaskGroup[] = [];
|
|
467
|
+
|
|
468
|
+
for (const spec of specs) {
|
|
469
|
+
const tasks: CompileTask[] = [];
|
|
470
|
+
|
|
471
|
+
for (const emitterConfig of getEmitterOptions(spec, flags.flavor, ctx)) {
|
|
472
|
+
// Apply flavor defaults first, then per-spec options so they can override
|
|
473
|
+
// (e.g. "generate-test": "false")
|
|
474
|
+
const opts: Record<string, unknown> = {};
|
|
475
|
+
for (const [k, v] of Object.entries(SpecialFlags[flags.flavor] ?? {})) {
|
|
476
|
+
opts[k] = v;
|
|
477
|
+
}
|
|
478
|
+
Object.assign(opts, emitterConfig);
|
|
479
|
+
|
|
480
|
+
opts["flavor"] = flags.flavor;
|
|
481
|
+
|
|
482
|
+
// Set output directory - tests/generated/<flavor>/<package> structure.
|
|
483
|
+
// Always anchored at <generatedFolder>/../tests/generated regardless of
|
|
484
|
+
// pluginDir, so generator-only checkouts work too.
|
|
485
|
+
const packageName = (opts["package-name"] as string) || defaultPackageName(spec, ctx);
|
|
486
|
+
const outputDir =
|
|
487
|
+
(opts["emitter-output-dir"] as string) ||
|
|
488
|
+
toPosix(`${ctx.generatedFolder}/../tests/generated/${flags.flavor}/${packageName}`);
|
|
489
|
+
opts["emitter-output-dir"] = outputDir;
|
|
490
|
+
|
|
491
|
+
if (flags.debug) {
|
|
492
|
+
opts["debug"] = true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
opts["examples-dir"] = toPosix(join(dirname(spec), "examples"));
|
|
496
|
+
|
|
497
|
+
if (options.emitYamlOnly) {
|
|
498
|
+
// Emit YAML only - Python processing is batched after all specs compile.
|
|
499
|
+
opts["emit-yaml-only"] = true;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
tasks.push({ spec, outputDir, options: opts });
|
|
411
503
|
}
|
|
412
|
-
|
|
413
|
-
|
|
504
|
+
|
|
505
|
+
groups.push({ spec, tasks });
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return groups;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export async function compileSpec(
|
|
512
|
+
task: CompileTask,
|
|
513
|
+
ctx: RegenerateContext,
|
|
514
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
515
|
+
const { spec, outputDir, options } = task;
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
const compilerOptions = {
|
|
519
|
+
emit: [ctx.pluginDir],
|
|
520
|
+
options: {
|
|
521
|
+
[ctx.emitterName]: options,
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const program = await compile(NodeHost, spec, compilerOptions);
|
|
526
|
+
|
|
527
|
+
if (program.hasError()) {
|
|
528
|
+
const errors = program.diagnostics
|
|
529
|
+
.filter((d) => d.severity === "error")
|
|
530
|
+
.map((d) => d.message)
|
|
531
|
+
.join("\n");
|
|
532
|
+
return { success: false, error: errors };
|
|
414
533
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
534
|
+
|
|
535
|
+
return { success: true };
|
|
536
|
+
} catch (err) {
|
|
537
|
+
rmSync(outputDir, { recursive: true, force: true });
|
|
538
|
+
return { success: false, error: String(err) };
|
|
420
539
|
}
|
|
421
|
-
return results;
|
|
422
540
|
}
|
|
423
541
|
|
|
424
|
-
export
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
542
|
+
export function renderProgressBar(
|
|
543
|
+
completed: number,
|
|
544
|
+
failed: number,
|
|
545
|
+
total: number,
|
|
546
|
+
width: number = 40,
|
|
547
|
+
): string {
|
|
548
|
+
const successCount = completed - failed;
|
|
549
|
+
const successWidth = Math.round((successCount / total) * width);
|
|
550
|
+
const failWidth = Math.round((failed / total) * width);
|
|
551
|
+
const emptyWidth = width - successWidth - failWidth;
|
|
552
|
+
|
|
553
|
+
const successBar = pc.bgGreen(" ".repeat(successWidth));
|
|
554
|
+
const failBar = failed > 0 ? pc.bgRed(" ".repeat(failWidth)) : "";
|
|
555
|
+
const emptyBar = pc.dim("░".repeat(Math.max(0, emptyWidth)));
|
|
556
|
+
|
|
557
|
+
const percent = Math.round((completed / total) * 100);
|
|
558
|
+
return `${successBar}${failBar}${emptyBar} ${pc.cyan(`${percent}%`)} (${completed}/${total})`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export async function runParallel(
|
|
562
|
+
groups: TaskGroup[],
|
|
563
|
+
maxJobs: number,
|
|
564
|
+
ctx: RegenerateContext,
|
|
565
|
+
): Promise<Map<string, boolean>> {
|
|
566
|
+
const results = new Map<string, boolean>();
|
|
428
567
|
const executing: Set<Promise<void>> = new Set();
|
|
429
568
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
569
|
+
const totalTasks = groups.reduce((sum, g) => sum + g.tasks.length, 0);
|
|
570
|
+
let completed = 0;
|
|
571
|
+
let failed = 0;
|
|
572
|
+
const failedSpecs: string[] = [];
|
|
573
|
+
|
|
574
|
+
const isTTY = process.stdout.isTTY;
|
|
575
|
+
|
|
576
|
+
const updateProgress = () => {
|
|
577
|
+
if (isTTY) {
|
|
578
|
+
process.stdout.write(`\r${renderProgressBar(completed, failed, totalTasks)}`);
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
updateProgress();
|
|
583
|
+
|
|
584
|
+
for (const group of groups) {
|
|
585
|
+
// Each group runs as a unit - tasks within a group run sequentially
|
|
586
|
+
// to avoid state pollution. Different groups run in parallel.
|
|
587
|
+
const runGroup = async () => {
|
|
588
|
+
const specDir = isAzureSpec(group.spec) ? ctx.azureHttpSpecs : ctx.httpSpecs;
|
|
589
|
+
const shortName = toPosix(relative(specDir, dirname(group.spec)));
|
|
590
|
+
|
|
591
|
+
let groupSuccess = true;
|
|
592
|
+
for (const task of group.tasks) {
|
|
593
|
+
const packageName = (task.options["package-name"] as string) || shortName;
|
|
594
|
+
|
|
595
|
+
const result = await compileSpec(task, ctx);
|
|
596
|
+
completed++;
|
|
597
|
+
|
|
598
|
+
if (!result.success) {
|
|
599
|
+
failed++;
|
|
600
|
+
failedSpecs.push(`${packageName}: ${result.error}`);
|
|
601
|
+
groupSuccess = false;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
updateProgress();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
results.set(group.spec, groupSuccess);
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const p = runGroup().finally(() => executing.delete(p));
|
|
433
611
|
executing.add(p);
|
|
434
612
|
|
|
435
|
-
|
|
436
|
-
if (executing.size >= poolLimit) {
|
|
613
|
+
if (executing.size >= maxJobs) {
|
|
437
614
|
await Promise.race(executing);
|
|
438
615
|
}
|
|
439
616
|
}
|
|
440
617
|
|
|
441
|
-
// Wait for remaining tasks
|
|
442
618
|
await Promise.all(executing);
|
|
619
|
+
|
|
620
|
+
if (isTTY) {
|
|
621
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (failedSpecs.length > 0) {
|
|
625
|
+
console.log(pc.red(`\nFailed specs:`));
|
|
626
|
+
for (const spec of failedSpecs) {
|
|
627
|
+
console.log(pc.red(` • ${spec}`));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return results;
|
|
443
632
|
}
|
|
444
633
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
634
|
+
/**
|
|
635
|
+
* Pre-create the marker files that the test harness expects to find before
|
|
636
|
+
* regeneration so it can verify they're cleared/preserved correctly.
|
|
637
|
+
*/
|
|
638
|
+
export async function preprocess(flavor: string, generatedFolder: string): Promise<void> {
|
|
639
|
+
if (flavor !== "azure") return;
|
|
640
|
+
|
|
641
|
+
const testsGeneratedDir = resolve(generatedFolder, "../tests/generated/azure");
|
|
642
|
+
|
|
643
|
+
const DELETE_CONTENT = "# This file is to be deleted after regeneration";
|
|
644
|
+
const KEEP_CONTENT = "# This file is to be kept after regeneration";
|
|
645
|
+
const DELETE_FILE = "to_be_deleted.py";
|
|
646
|
+
const entries: { folder: string[]; file: string; content: string }[] = [
|
|
647
|
+
{
|
|
648
|
+
folder: ["authentication-api-key", "authentication", "apikey", "_operations"],
|
|
649
|
+
file: DELETE_FILE,
|
|
650
|
+
content: DELETE_CONTENT,
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
folder: ["generation-subdir", "generation", "subdir", "_generated"],
|
|
654
|
+
file: DELETE_FILE,
|
|
655
|
+
content: DELETE_CONTENT,
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
folder: ["generation-subdir", "generated_tests"],
|
|
659
|
+
file: DELETE_FILE,
|
|
660
|
+
content: DELETE_CONTENT,
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
folder: ["generation-subdir", "generation", "subdir"],
|
|
664
|
+
file: "to_be_kept.py",
|
|
665
|
+
content: KEEP_CONTENT,
|
|
666
|
+
},
|
|
667
|
+
];
|
|
668
|
+
|
|
669
|
+
await Promise.all(
|
|
670
|
+
entries.map(async ({ folder, file, content }) => {
|
|
671
|
+
const targetFolder = join(testsGeneratedDir, ...folder);
|
|
672
|
+
await mkdir(targetFolder, { recursive: true });
|
|
673
|
+
await writeFile(join(targetFolder, file), content);
|
|
674
|
+
}),
|
|
675
|
+
);
|
|
676
|
+
}
|
|
470
677
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Resets the `tests/generated/{azure,unbranded}` baseline by sparse-checking-out
|
|
680
|
+
* `eng/tools/emitter/gen` from the Azure/azure-sdk-for-python repo, then
|
|
681
|
+
* deleting a couple of fully-generated package folders so regeneration has to
|
|
682
|
+
* recreate them from scratch (smoke test of full-emit path).
|
|
683
|
+
*
|
|
684
|
+
* `generatedFolder` is the per-repo `generator/` directory; baseline lands at
|
|
685
|
+
* `<generatedFolder>/../tests/generated`.
|
|
686
|
+
*/
|
|
687
|
+
export async function prepareBaselineOfGeneratedCode(generatedFolder: string): Promise<void> {
|
|
688
|
+
const repoUrl = "https://github.com/Azure/azure-sdk-for-python.git";
|
|
689
|
+
const branch = "main";
|
|
690
|
+
const sourceSubdir = "eng/tools/emitter/gen";
|
|
691
|
+
const testsGeneratedDir = resolve(generatedFolder, "../tests/generated");
|
|
692
|
+
|
|
693
|
+
console.log(pc.cyan(`\n${"=".repeat(60)}`));
|
|
694
|
+
console.log(pc.cyan(`Resetting baseline from ${repoUrl} (${branch}/${sourceSubdir})`));
|
|
695
|
+
console.log(pc.cyan(`${"=".repeat(60)}\n`));
|
|
696
|
+
|
|
697
|
+
// Wipe tests/generated
|
|
698
|
+
if (existsSync(testsGeneratedDir)) {
|
|
699
|
+
console.log(pc.dim(`Removing ${testsGeneratedDir}`));
|
|
700
|
+
rmSync(testsGeneratedDir, { recursive: true, force: true });
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Sparse-checkout the baseline folder into a temp directory
|
|
704
|
+
const tempDir = await mkdtemp(join(tmpdir(), "azsdk-baseline-"));
|
|
705
|
+
try {
|
|
706
|
+
console.log(pc.dim(`Cloning into ${tempDir}`));
|
|
707
|
+
const run = (cmd: string) =>
|
|
708
|
+
execSync(cmd, { cwd: tempDir, stdio: ["ignore", "ignore", "inherit"] });
|
|
709
|
+
|
|
710
|
+
run(`git init`);
|
|
711
|
+
run(`git remote add origin ${repoUrl}`);
|
|
712
|
+
run(`git config core.sparseCheckout true`);
|
|
713
|
+
run(`git sparse-checkout init --cone`);
|
|
714
|
+
run(`git sparse-checkout set ${sourceSubdir}`);
|
|
715
|
+
run(`git fetch --depth 1 origin ${branch}`);
|
|
716
|
+
run(`git checkout FETCH_HEAD`);
|
|
717
|
+
|
|
718
|
+
const sourceRoot = join(tempDir, ...sourceSubdir.split("/"));
|
|
719
|
+
for (const flavor of ["azure", "unbranded"]) {
|
|
720
|
+
const src = join(sourceRoot, flavor);
|
|
721
|
+
const dest = join(testsGeneratedDir, flavor);
|
|
722
|
+
if (!existsSync(src)) {
|
|
723
|
+
console.warn(pc.yellow(`Baseline folder not found: ${src}`));
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
console.log(pc.dim(`Copying ${flavor}/ -> ${dest}`));
|
|
727
|
+
await cp(src, dest, { recursive: true });
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
console.log(pc.green(`Baseline reset complete.\n`));
|
|
731
|
+
} finally {
|
|
732
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Delete a couple of fully-generated package folders so regeneration has to
|
|
736
|
+
// recreate them from scratch (smoke test of full-emit path).
|
|
737
|
+
const targetsToDelete = [
|
|
738
|
+
join(testsGeneratedDir, "azure", "authentication-http-custom"),
|
|
739
|
+
join(testsGeneratedDir, "unbranded", "encode-array"),
|
|
740
|
+
];
|
|
741
|
+
for (const target of targetsToDelete) {
|
|
742
|
+
if (existsSync(target)) {
|
|
743
|
+
console.log(pc.dim(`Deleting ${target}`));
|
|
744
|
+
rmSync(target, { recursive: true, force: true });
|
|
745
|
+
}
|
|
475
746
|
}
|
|
476
747
|
}
|