Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(remodel): call ubergen to create aws-cdk-lib #24263

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"jsii-reflect": "1.74.0",
"jsii-rosetta": "1.74.0",
"lerna": "^4.0.0",
"remodel": "0.0.0",
"@aws-cdk/remodel": "0.0.0",
"patch-package": "^6.5.1",
"semver": "^6.3.0",
"standard-version": "^9.5.0",
Expand Down
19 changes: 17 additions & 2 deletions tools/@aws-cdk/cfn2ts/lib/augmentation-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,26 @@ import { SpecName } from './spec-utils';
* <ClassBase>.prototype.<method> = // ...impl...
* ```
*/
export interface AugmentationsGeneratorOptions {
/**
* Path of cloudwatch import to use when generating augmentation source
* files.
*
* @default '@aws-cdk/aws-cloudwatch'
*/
cloudwatchImport?: string;
}

export class AugmentationGenerator {
public readonly outputFile: string;
private readonly code = new CodeMaker();

constructor(moduleName: string, private readonly spec: schema.Specification, private readonly affix: string) {
constructor(
moduleName: string,
private readonly spec: schema.Specification,
private readonly affix: string,
private readonly config?: AugmentationsGeneratorOptions,
) {
this.outputFile = `${moduleName}-augmentations.generated.ts`;
this.code.openFile(this.outputFile);

Expand All @@ -53,7 +68,7 @@ export class AugmentationGenerator {

if (aug.metrics) {
if (!importedCloudWatch) {
this.code.line("import * as cloudwatch from '@aws-cdk/aws-cloudwatch';");
this.code.line(`import * as cloudwatch from '${this.config?.cloudwatchImport ?? '@aws-cdk/aws-cloudwatch'}';`);
importedCloudWatch = true;
}
this.emitMetricAugmentations(resourceTypeName, aug.metrics, aug.options);
Expand Down
174 changes: 115 additions & 59 deletions tools/@aws-cdk/cfn2ts/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import * as path from 'path';
import * as cfnSpec from '@aws-cdk/cfnspec';
import * as pkglint from '@aws-cdk/pkglint';
import * as fs from 'fs-extra';
import { AugmentationGenerator } from './augmentation-generator';
import { AugmentationGenerator, AugmentationsGeneratorOptions } from './augmentation-generator';
import { CannedMetricsGenerator } from './canned-metrics-generator';
import CodeGenerator, { CodeGeneratorOptions } from './codegen';
import { packageName } from './genspec';

export default async function(scopes: string | string[], outPath: string, options: CodeGeneratorOptions = { }): Promise<void> {
export default async function generate(
scopes: string | string[],
outPath: string,
options: CodeGeneratorOptions & AugmentationsGeneratorOptions = { },
): Promise<string[]> {
const outputFiles: string[] = [];
if (outPath !== '.') { await fs.mkdirp(outPath); }

if (scopes === '*') {
Expand All @@ -27,82 +32,119 @@ export default async function(scopes: string | string[], outPath: string, option
const generator = new CodeGenerator(name, spec, affix, options);
generator.emitCode();
await generator.save(outPath);
outputFiles.push(generator.outputFile);

const augs = new AugmentationGenerator(name, spec, affix);
const augs = new AugmentationGenerator(name, spec, affix, options);
if (augs.emitCode()) {
await augs.save(outPath);
outputFiles.push(augs.outputFile);
}

const canned = new CannedMetricsGenerator(name, scope);
if (canned.generate()) {
await canned.save(outPath);
outputFiles.push(canned.outputFile);
}
}

return outputFiles;
}

/**
* Configuration options for the generateAll function
*/
export interface GenerateAllOptions extends CodeGeneratorOptions, AugmentationsGeneratorOptions {
/**
* Path of the file containing the map of module names to their CFN Scopes
*/
scopeMapPath: string;
}

/**
* A data structure holding information about generated modules. It maps
* module names to their full module definition and their CFN scopes.
*/
export interface ModuleMap {
[moduleName: string]: {
module?: pkglint.ModuleDefinition;
scopes: string[];
}
}

export async function generateAll(outPath: string, options: CodeGeneratorOptions): Promise<pkglint.ModuleDefinition[]> {
/**
* Generates L1s for all submodules of a monomodule. Modules to generate are
* chosen based on the contents of the `scopeMapPath` file. This is intended for
* use in generated L1s in aws-cdk-lib.
* @param outPath The root directory to generate L1s in
* @param param1 Options
* @returns A ModuleMap containing the ModuleDefinition and CFN scopes for each generated module.
*/
export async function generateAll(
outPath: string,
{ scopeMapPath, ...options }: GenerateAllOptions,
): Promise<ModuleMap> {
const scopes = cfnSpec.namespaces();
const modules = new Array<pkglint.ModuleDefinition>();
const moduleMap = await readScopeMap(scopeMapPath);

// Make sure all scopes have their own dedicated package/namespace.
// Adds new submodules for new namespaces.
for (const scope of scopes) {
const spec = cfnSpec.filteredSpecification(s => s.startsWith(`${scope}::`));
const module = pkglint.createModuleDefinitionFromCfnNamespace(scope);
const packagePath = path.join(outPath, module.moduleName);
const libPath = path.join(packagePath, 'lib');

modules.push(module);

if (Object.keys(spec.ResourceTypes).length === 0) {
throw new Error(`No resource was found for scope ${scope}`);
}
const name = packageName(scope);
const affix = computeAffix(scope, scopes);

const generator = new CodeGenerator(name, spec, affix, options);
generator.emitCode();
await generator.save(libPath);
const outputFiles = [generator.outputFile];

const augs = new AugmentationGenerator(name, spec, affix);
if (augs.emitCode()) {
await augs.save(libPath);
outputFiles.push(augs.outputFile);
}

const canned = new CannedMetricsGenerator(name, scope);
if (canned.generate()) {
await canned.save(libPath);
outputFiles.push(canned.outputFile);
}

// Create index.ts file if needed
if (!fs.existsSync(path.join(packagePath, 'index.ts'))) {
const lines = [`// ${scope} CloudFormation Resources:`];
lines.push(...outputFiles.map((f) => `export * from './lib/${f.replace('.ts', '')}'`));

await fs.writeFile(path.join(packagePath, 'index.ts'), lines.join('\n') + '\n');
}
const currentScopes = moduleMap[module.moduleName]?.scopes ?? [];
// remove dupes
const newScopes = [...new Set([...currentScopes, scope])];

// Add new modules to module map and return to caller
moduleMap[module.moduleName] = {
scopes: newScopes,
module,
};
}

// Create .jsiirc.json file if needed
if (!fs.existsSync(path.join(packagePath, '.jsiirc.json'))) {
const jsiirc = {
targets: {
java: {
package: module.javaPackage,
},
dotnet: {
package: module.dotnetPackage,
await Promise.all(Object.entries(moduleMap).map(
async ([moduleName, { scopes: moduleScopes, module }]) => {
const packagePath = path.join(outPath, moduleName);
const sourcePath = path.join(packagePath, 'lib');

const outputFiles = await generate(moduleScopes, sourcePath, options);

if (!fs.existsSync(path.join(packagePath, 'index.ts'))) {
let lines = moduleScopes.map((s: string) => `// ${s} Cloudformation Resources`);
lines.push(...outputFiles.map((f) => `export * from './lib/${f.replace('.ts', '')}'`));

await fs.writeFile(path.join(packagePath, 'index.ts'), lines.join('\n') + '\n');
}

// Create .jsiirc.json file if needed
const excludeJsii = ['core'];
if (
!fs.existsSync(path.join(packagePath, '.jsiirc.json'))
&& !excludeJsii.includes(moduleName)
) {
if (!module) {
throw new Error(
`Cannot infer path or namespace for submodule named "${moduleName}". Manually create ${packagePath}/.jsiirc.json file.`,
);
}

const jsiirc = {
targets: {
java: {
package: module.javaPackage,
},
dotnet: {
package: module.dotnetPackage,
},
python: {
module: module.pythonModuleName,
},
},
python: {
module: module.pythonModuleName,
},
},
};
await fs.writeJson(path.join(packagePath, '.jsiirc.json'), jsiirc, { spaces: 2 });
}
}
};
await fs.writeJson(path.join(packagePath, '.jsiirc.json'), jsiirc, { spaces: 2 });
}
}));

return modules;
return moduleMap;
}

/**
Expand All @@ -125,3 +167,17 @@ function computeAffix(scope: string, allScopes: string[]): string {
}
return '';
}

/**
* Reads the scope map from a file and transforms it into the type we need.
*/
async function readScopeMap(filepath: string) : Promise<ModuleMap> {
const scopeMap: Record<string, string[]> = await fs.readJson(filepath);
return Object.entries(scopeMap)
.reduce((accum, [name, moduleScopes]) => {
return {
...accum,
[name]: { scopes: moduleScopes },
};
}, {});
}
6 changes: 6 additions & 0 deletions tools/@aws-cdk/remodel/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc');
baseConfig.parserOptions.project = __dirname + '/tsconfig.json';
module.exports = {
...baseConfig,
ignorePatterns: [...baseConfig.ignorePatterns, 'lib/template'],
};
2 changes: 2 additions & 0 deletions tools/@aws-cdk/remodel/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ dist
!.eslintrc.js
!config/*.js
junit.xml

.LAST_BUILD
7 changes: 7 additions & 0 deletions tools/@aws-cdk/remodel/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

.LAST_BUILD
*.snk
junit.xml
.eslintrc.js
# exclude cdk artifacts
**/cdk.out
Loading