packages/jsii-pacmak/lib/targets/dotnet/filegenerator.ts (165 lines of code) (raw):

import { Assembly } from '@jsii/spec'; import { CodeMaker } from 'codemaker'; import * as path from 'path'; import * as xmlbuilder from 'xmlbuilder'; import { TargetName } from '..'; import * as logging from '../../logging'; import { VERSION } from '../../version'; import { TARGET_FRAMEWORK } from '../dotnet'; import { toNuGetVersionRange, toReleaseVersion } from '../version-utils'; import { DotNetNameUtils } from './nameutils'; // Represents a dependency in the dependency tree. export class DotNetDependency { public readonly version: string; public constructor( public readonly namespace: string, public readonly packageId: string, public readonly fqn: string, version: string, public readonly partOfCompilation: boolean, ) { this.version = toNuGetVersionRange(version); } } // Generates misc files such as the .csproj and the AssemblyInfo.cs file // Uses the same instance of CodeMaker as the rest of the code so that the files get created when calling the save() method export class FileGenerator { private readonly assm: Assembly; private readonly tarballFileName: string; private readonly code: CodeMaker; private readonly assemblyInfoNamespaces: string[] = [ 'Amazon.JSII.Runtime.Deputy', ]; private readonly nameutils: DotNetNameUtils = new DotNetNameUtils(); // We pass in an instance of CodeMaker so that the files get later saved // when calling the save() method on the .NET Generator. public constructor(assm: Assembly, tarballFileName: string, code: CodeMaker) { this.assm = assm; this.tarballFileName = tarballFileName; this.code = code; } // Generates the .csproj file public generateProjectFile( dependencies: Map<string, DotNetDependency>, iconFile?: string, ) { const assembly = this.assm; const packageId: string = assembly.targets!.dotnet!.packageId; const projectFilePath: string = path.join(packageId, `${packageId}.csproj`); // Construct XML csproj content. // headless removes the <xml?> head node so that the first node is the <Project> node const rootNode = xmlbuilder.create('Project', { encoding: 'UTF-8', headless: true, }); rootNode.att('Sdk', 'Microsoft.NET.Sdk'); const propertyGroup = rootNode.ele('PropertyGroup'); const dotnetInfo = assembly.targets!.dotnet; propertyGroup.comment('Package Identification'); propertyGroup.ele('Description', this.getDescription()); if (iconFile != null) { propertyGroup.ele('PackageIcon', iconFile.split(/[/\\]+/).join('\\')); // We also need to actually include the icon in the package const noneNode = rootNode.ele('ItemGroup').ele('None'); noneNode.att('Include', iconFile.split(/[/\\]+/).join('\\')); noneNode.att('Pack', 'true'); noneNode.att( 'PackagePath', `\\${path .dirname(iconFile) .split(/[/\\]+/) .join('\\')}`, ); } // We continue to include the PackageIconUrl even if we put PackageIcon for backwards compatibility, as suggested // by https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#packageicon if (dotnetInfo!.iconUrl != null) { propertyGroup.ele('PackageIconUrl', dotnetInfo!.iconUrl); } propertyGroup.ele('PackageId', packageId); propertyGroup.ele('PackageLicenseExpression', assembly.license); propertyGroup.ele('PackageVersion', this.getDecoratedVersion(assembly)); if (dotnetInfo!.title != null) { propertyGroup.ele('Title', dotnetInfo!.title); } propertyGroup.comment('Additional Metadata'); propertyGroup.ele('Authors', assembly.author.name); if (assembly.author.organization) { propertyGroup.ele('Company', assembly.author.name); } if (assembly.keywords) { propertyGroup.ele('PackageTags', assembly.keywords.join(';')); } propertyGroup.ele('Language', 'en-US'); propertyGroup.ele('ProjectUrl', assembly.homepage); propertyGroup.ele('RepositoryUrl', assembly.repository.url); propertyGroup.ele('RepositoryType', assembly.repository.type); propertyGroup.comment('Build Configuration'); propertyGroup.ele('GenerateDocumentationFile', 'true'); propertyGroup.ele('GeneratePackageOnBuild', 'true'); propertyGroup.ele('IncludeSymbols', 'true'); propertyGroup.ele('IncludeSource', 'true'); propertyGroup.ele('Nullable', 'enable'); propertyGroup.ele('SymbolPackageFormat', 'snupkg'); propertyGroup.ele('TargetFramework', TARGET_FRAMEWORK); // Transparently rolll forward across major SDK releases if needed propertyGroup.ele('RollForward', 'Major'); const itemGroup1 = rootNode.ele('ItemGroup'); const embeddedResource = itemGroup1.ele('EmbeddedResource'); embeddedResource.att('Include', this.tarballFileName); const itemGroup2 = rootNode.ele('ItemGroup'); const packageReference = itemGroup2.ele('PackageReference'); packageReference.att('Include', 'Amazon.JSII.Runtime'); packageReference.att('Version', toNuGetVersionRange(`^${VERSION}`)); dependencies.forEach((value: DotNetDependency) => { if (value.partOfCompilation) { const dependencyReference = itemGroup2.ele('ProjectReference'); dependencyReference.att( 'Include', `../${value.packageId}/${value.packageId}.csproj`, ); } else { const dependencyReference = itemGroup2.ele('PackageReference'); dependencyReference.att('Include', value.packageId); dependencyReference.att('Version', value.version); } }); const warnings = rootNode.ele('PropertyGroup'); // Suppress warnings about [Obsolete] members, this is the author's choice! warnings.comment('Silence [Obsolete] warnings'); warnings.ele('NoWarn').text('0612,0618'); // Treat select warnings as errors, as these are likely codegen bugs: warnings.comment( 'Treat warnings symptomatic of code generation bugs as errors', ); warnings.ele( 'WarningsAsErrors', [ '0108', // 'member1' hides inherited member 'member2'. Use the new keyword if hiding was intended. '0109', // The member 'member' does not hide an inherited member. The new keyword is not required. ].join(','), ); const xml = rootNode.end({ pretty: true, spaceBeforeSlash: true }); // Sending the xml content to the codemaker to ensure the file is written // and added to the file list for tracking this.code.openFile(projectFilePath); this.code.open(xml); // Unindent for the next file this.code.close(); this.code.closeFile(projectFilePath); logging.debug(`Written to ${projectFilePath}`); } // Generates the AssemblyInfo.cs file public generateAssemblyInfoFile() { const packageId: string = this.assm.targets!.dotnet!.packageId; const filePath: string = path.join(packageId, 'AssemblyInfo.cs'); this.code.openFile(filePath); this.assemblyInfoNamespaces.map((n) => this.code.line(`using ${n};`)); this.code.line(); const assembly = `[assembly: JsiiAssembly("${this.assm.name}", "${this.assm.version}", "${this.tarballFileName}")]`; this.code.line(assembly); this.code.closeFile(filePath); } // Generates the description private getDescription(): string { const docs = this.assm.docs; if (docs) { const stability = docs.stability; if (stability) { return `${ this.assm.description } (Stability: ${this.nameutils.capitalizeWord(stability)})`; } } return this.assm.description; } // Generates the decorated version private getDecoratedVersion(assembly: Assembly): string { const suffix = assembly.targets!.dotnet!.versionSuffix; if (suffix) { // suffix is guaranteed to start with a leading `-` return `${assembly.version}${suffix}`; } return toReleaseVersion(assembly.version, TargetName.DOTNET); } }