diff --git a/packages/@jsii/spec/lib/assembly.ts b/packages/@jsii/spec/lib/assembly.ts index d3c21db5fc..779fff7d16 100644 --- a/packages/@jsii/spec/lib/assembly.ts +++ b/packages/@jsii/spec/lib/assembly.ts @@ -3,7 +3,7 @@ export const SPEC_FILE_NAME = '.jsii'; /** * A JSII assembly specification. */ -export interface Assembly extends Documentable { +export interface Assembly extends AssemblyConfiguration, Documentable { /** * The version of the spec schema */ @@ -88,14 +88,6 @@ export interface Assembly extends Documentable { */ license: string; - /** - * A map of target name to configuration, which is used when generating - * packages for various languages. - * - * @default none - */ - targets?: AssemblyTargets; - /** * Arbitrary key-value pairs of metadata, which the maintainer chose to * document with the assembly. These entries do not carry normative @@ -115,18 +107,20 @@ export interface Assembly extends Documentable { /** * Direct dependencies on other assemblies (with semver), the key is the JSII - * assembly name. + * assembly name, and the value is a SemVer expression.. * * @default none */ - dependencies?: { [assembly: string]: PackageVersion }; + dependencies?: { [assembly: string]: string }; /** - * Closure of all dependency assemblies, direct and transitive. + * Target configuration for all the assemblies that are direct or transitive + * dependencies of this assembly. This is needed to generate correct native + * type names for any transitively inherited member, in certain languages. * * @default none */ - dependencyClosure?: { [assembly: string]: PackageVersion }; + dependencyClosure?: { [assembly: string]: AssemblyConfiguration }; /** * List if bundled dependencies (these are not expected to be jsii @@ -151,6 +145,19 @@ export interface Assembly extends Documentable { readme?: { markdown: string }; } +/** + * Shareable configuration of a jsii Assembly. + */ +export interface AssemblyConfiguration { + /** + * A map of target name to configuration, which is used when generating + * packages for various languages. + * + * @default none + */ + targets?: AssemblyTargets; +} + /** * Versions of the JSII Assembly Specification. */ @@ -214,25 +221,6 @@ export interface AssemblyTargets { [language: string]: { [key: string]: any } | undefined; } -/** - * The version of a package. - */ -export interface PackageVersion { - /** - * Version of the package. - * - * @minLength 1 - */ - version: string; - - /** - * Targets for a given assembly. - * - * @default none - */ - targets?: AssemblyTargets; -} - /** * Where in the module source the definition for this API item was found */ diff --git a/packages/@jsii/spec/lib/index.ts b/packages/@jsii/spec/lib/index.ts index 27644b56f8..4b318ab612 100644 --- a/packages/@jsii/spec/lib/index.ts +++ b/packages/@jsii/spec/lib/index.ts @@ -1,4 +1,4 @@ export * from './assembly'; +export * from './configuration'; export * from './name-tree'; export * from './validate-assembly'; -export * from './configuration'; diff --git a/packages/@scope/jsii-calc-base/package.json b/packages/@scope/jsii-calc-base/package.json index 9f2c1dc38b..5ed761195a 100644 --- a/packages/@scope/jsii-calc-base/package.json +++ b/packages/@scope/jsii-calc-base/package.json @@ -29,10 +29,10 @@ "test:update": "npm run build && UPDATE_DIFF=1 npm run test" }, "dependencies": { - "@scope/jsii-calc-base-of-base": "^0.20.11" + "@scope/jsii-calc-base-of-base": "0.20.11" }, "peerDependencies": { - "@scope/jsii-calc-base-of-base": "^0.20.11" + "@scope/jsii-calc-base-of-base": "0.20.11" }, "devDependencies": { "@types/node": "^10.17.13", diff --git a/packages/@scope/jsii-calc-base/test/assembly.jsii b/packages/@scope/jsii-calc-base/test/assembly.jsii index f947cca587..201ae10995 100644 --- a/packages/@scope/jsii-calc-base/test/assembly.jsii +++ b/packages/@scope/jsii-calc-base/test/assembly.jsii @@ -8,29 +8,7 @@ "url": "https://aws.amazon.com" }, "dependencies": { - "@scope/jsii-calc-base-of-base": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId" - }, - "java": { - "maven": { - "artifactId": "calculator-base-of-base", - "groupId": "software.amazon.jsii.tests" - }, - "package": "software.amazon.jsii.tests.calculator.baseofbase" - }, - "js": { - "npm": "@scope/jsii-calc-base-of-base" - }, - "python": { - "distName": "scope.jsii-calc-base-of-base", - "module": "scope.jsii_calc_base_of_base" - } - }, - "version": "0.20.11" - } + "@scope/jsii-calc-base-of-base": "0.20.11" }, "dependencyClosure": { "@scope/jsii-calc-base-of-base": { @@ -53,8 +31,7 @@ "distName": "scope.jsii-calc-base-of-base", "module": "scope.jsii_calc_base_of_base" } - }, - "version": "0.20.11" + } } }, "description": "An example direct dependency for jsii-calc.", @@ -174,5 +151,5 @@ } }, "version": "0.20.11", - "fingerprint": "lSQlk5mMPd7fxaZoCdCekSFY4rDOAu3VnLuIcFUXA6o=" + "fingerprint": "vaUHiWCfpSsCfXz18WPVQ3HhCBTPj23pDky4IqmEWxw=" } diff --git a/packages/@scope/jsii-calc-lib/test/assembly.jsii b/packages/@scope/jsii-calc-lib/test/assembly.jsii index aee0bcedeb..2aa69c9263 100644 --- a/packages/@scope/jsii-calc-lib/test/assembly.jsii +++ b/packages/@scope/jsii-calc-lib/test/assembly.jsii @@ -8,29 +8,7 @@ "url": "https://aws.amazon.com" }, "dependencies": { - "@scope/jsii-calc-base": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BasePackageId" - }, - "java": { - "maven": { - "artifactId": "calculator-base", - "groupId": "software.amazon.jsii.tests" - }, - "package": "software.amazon.jsii.tests.calculator.base" - }, - "js": { - "npm": "@scope/jsii-calc-base" - }, - "python": { - "distName": "scope.jsii-calc-base", - "module": "scope.jsii_calc_base" - } - }, - "version": "0.20.11" - } + "@scope/jsii-calc-base": "^0.20.11" }, "dependencyClosure": { "@scope/jsii-calc-base": { @@ -53,8 +31,7 @@ "distName": "scope.jsii-calc-base", "module": "scope.jsii_calc_base" } - }, - "version": "0.20.11" + } }, "@scope/jsii-calc-base-of-base": { "targets": { @@ -76,8 +53,7 @@ "distName": "scope.jsii-calc-base-of-base", "module": "scope.jsii_calc_base_of_base" } - }, - "version": "0.20.11" + } } }, "description": "A simple calcuator library built on JSII.", @@ -540,5 +516,5 @@ } }, "version": "0.20.11", - "fingerprint": "WSHD7tywHgFC9jmImvDuy7NGYCDWAAb49jk0ZPzCoFM=" + "fingerprint": "tdRWsMWbPQxej79pQ39gbrrViv/Wl6vfTLY59IJPi4w=" } diff --git a/packages/jsii-calc/test/assembly.jsii b/packages/jsii-calc/test/assembly.jsii index 841f093220..14afe903a7 100644 --- a/packages/jsii-calc/test/assembly.jsii +++ b/packages/jsii-calc/test/assembly.jsii @@ -34,77 +34,9 @@ } ], "dependencies": { - "@scope/jsii-calc-base": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BasePackageId" - }, - "java": { - "maven": { - "artifactId": "calculator-base", - "groupId": "software.amazon.jsii.tests" - }, - "package": "software.amazon.jsii.tests.calculator.base" - }, - "js": { - "npm": "@scope/jsii-calc-base" - }, - "python": { - "distName": "scope.jsii-calc-base", - "module": "scope.jsii_calc_base" - } - }, - "version": "0.20.11" - }, - "@scope/jsii-calc-base-of-base": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId" - }, - "java": { - "maven": { - "artifactId": "calculator-base-of-base", - "groupId": "software.amazon.jsii.tests" - }, - "package": "software.amazon.jsii.tests.calculator.baseofbase" - }, - "js": { - "npm": "@scope/jsii-calc-base-of-base" - }, - "python": { - "distName": "scope.jsii-calc-base-of-base", - "module": "scope.jsii_calc_base_of_base" - } - }, - "version": "0.20.11" - }, - "@scope/jsii-calc-lib": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.LibNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.LibPackageId", - "versionSuffix": "-devpreview" - }, - "java": { - "maven": { - "artifactId": "calculator-lib", - "groupId": "software.amazon.jsii.tests", - "versionSuffix": ".DEVPREVIEW" - }, - "package": "software.amazon.jsii.tests.calculator.lib" - }, - "js": { - "npm": "@scope/jsii-calc-lib" - }, - "python": { - "distName": "scope.jsii-calc-lib", - "module": "scope.jsii_calc_lib" - } - }, - "version": "0.20.11" - } + "@scope/jsii-calc-base": "^0.20.11", + "@scope/jsii-calc-base-of-base": "^0.20.11", + "@scope/jsii-calc-lib": "^0.20.11" }, "dependencyClosure": { "@scope/jsii-calc-base": { @@ -127,8 +59,7 @@ "distName": "scope.jsii-calc-base", "module": "scope.jsii_calc_base" } - }, - "version": "0.20.11" + } }, "@scope/jsii-calc-base-of-base": { "targets": { @@ -150,8 +81,7 @@ "distName": "scope.jsii-calc-base-of-base", "module": "scope.jsii_calc_base_of_base" } - }, - "version": "0.20.11" + } }, "@scope/jsii-calc-lib": { "targets": { @@ -175,8 +105,7 @@ "distName": "scope.jsii-calc-lib", "module": "scope.jsii_calc_lib" } - }, - "version": "0.20.11" + } } }, "description": "A simple calcuator built on JSII.", @@ -12093,5 +12022,5 @@ } }, "version": "0.20.11", - "fingerprint": "5M7u8+bWxu4amY/gs4Z/K4IpYqJCMGtnQiVoerS4uac=" + "fingerprint": "j7Hsf1Hd/mih3Gr3UR7vYcXeLrbixxffBTzvSokikjM=" } diff --git a/packages/jsii-pacmak/lib/generator.ts b/packages/jsii-pacmak/lib/generator.ts index 14166318e5..bc9aa592b8 100644 --- a/packages/jsii-pacmak/lib/generator.ts +++ b/packages/jsii-pacmak/lib/generator.ts @@ -502,7 +502,7 @@ export abstract class Generator implements IGenerator { * Looks up a jsii module in the dependency tree. * @param name The name of the jsii module to look up */ - protected findModule(name: string): spec.PackageVersion { + protected findModule(name: string): spec.AssemblyConfiguration { // if this is the current module, return it if (this.assembly.name === name) { @@ -515,7 +515,7 @@ export abstract class Generator implements IGenerator { return found; } - throw new Error(`Unable to find module ${name} as a direct or indirect dependency of ${this.assembly.name}`); + throw new Error(`Unable to find module ${name} as a dependency of ${this.assembly.name}`); } protected findType(fqn: string): spec.Type { diff --git a/packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts b/packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts index 9c1a1d111a..346e1157bc 100644 --- a/packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts +++ b/packages/jsii-pacmak/lib/targets/dotnet/dotnetgenerator.ts @@ -79,6 +79,9 @@ export class DotNetGenerator extends Generator { await fs.mkdirp(path.join(outdir, packageId)); await fs.copyFile(tarball, path.join(outdir, packageId, tarballFileName)); + // Create an anchor file for the current model + this.generateDependencyAnchorFile(); + // Copying the .jsii file await fs.copyFile(this.jsiiFilePath, path.join(outdir, packageId, spec.SPEC_FILE_NAME)); @@ -86,6 +89,20 @@ export class DotNetGenerator extends Generator { return this.code.save(outdir); } + /** + * Generates the anchor file + */ + protected generateDependencyAnchorFile() { + const namespace = `${this.assembly.targets!.dotnet!.namespace}.Internal.DependencyResolution`; + this.openFileIfNeeded('Anchor', namespace, false, false); + this.code.openBlock('public sealed class Anchor'); + this.code.openBlock('public Anchor()'); + this.typeresolver.namespaceDependencies.forEach(value => this.code.line(`new ${value.namespace}.Internal.DependencyResolution.Anchor();`)); + this.code.closeBlock(); + this.code.closeBlock(); + this.closeFileIfNeeded('Anchor', namespace, false); + } + /** * Not used as we override the save() method */ diff --git a/packages/jsii-pacmak/lib/targets/dotnet/dotnettyperesolver.ts b/packages/jsii-pacmak/lib/targets/dotnet/dotnettyperesolver.ts index 66f3efb26b..f809520466 100644 --- a/packages/jsii-pacmak/lib/targets/dotnet/dotnettyperesolver.ts +++ b/packages/jsii-pacmak/lib/targets/dotnet/dotnettyperesolver.ts @@ -2,7 +2,7 @@ import * as spec from '@jsii/spec'; import { DotNetDependency } from './filegenerator'; import { DotNetNameUtils } from './nameutils'; -type FindModuleCallback = (fqn: string) => spec.Assembly | spec.PackageVersion; +type FindModuleCallback = (fqn: string) => spec.AssemblyConfiguration; type FindTypeCallback = (fqn: string) => spec.Type; export class DotNetTypeResolver { @@ -72,23 +72,21 @@ export class DotNetTypeResolver { */ public resolveNamespacesDependencies(): void { const assmDependencies = this.assembly.dependencies ?? {}; - for (const depName of Object.keys(assmDependencies)) { - const depInfo = assmDependencies[depName]; + const assmConfigurations = this.assembly.dependencyClosure ?? {}; + for (const [depName, version] of Object.entries(assmDependencies)) { + const depInfo = assmConfigurations[depName]; if (!this.namespaceDependencies.has(depName)) { - const dotnetInfo = depInfo.targets!.dotnet; - const namespace = dotnetInfo!.namespace; - const packageId = dotnetInfo!.packageId; - let version = depInfo.version; + const dotnetInfo = depInfo.targets!.dotnet!; + const namespace = dotnetInfo.namespace; + const packageId = dotnetInfo.packageId; const suffix = depInfo.targets!.dotnet!.versionSuffix; - if (suffix) { - // suffix is guaranteed to start with a leading `-` - version = `${depInfo.version}${suffix}`; - } + this.namespaceDependencies.set(depName, new DotNetDependency( namespace, packageId, depName, - version, + // suffix, when present, is guaranteed to start with a leading `-` + suffix ? `${version}${suffix}` : version, this.assembliesCurrentlyBeingCompiled.includes(depName))); } } diff --git a/packages/jsii-pacmak/lib/targets/dotnet/filegenerator.ts b/packages/jsii-pacmak/lib/targets/dotnet/filegenerator.ts index ace5d6cb3b..e372267753 100644 --- a/packages/jsii-pacmak/lib/targets/dotnet/filegenerator.ts +++ b/packages/jsii-pacmak/lib/targets/dotnet/filegenerator.ts @@ -6,15 +6,20 @@ import { DotNetNameUtils } from './nameutils'; import * as logging from '../../logging'; import { nextMajorVersion } from '../../util'; import { TARGET_FRAMEWORK } from '../dotnet'; +import { toNuGetVersionRange } from '../version-utils'; // 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, - public readonly version: string, - public readonly partOfCompilation: boolean) { + version: string, + public readonly partOfCompilation: boolean + ) { + this.version = toNuGetVersionRange(version); } } diff --git a/packages/jsii-pacmak/lib/targets/java.ts b/packages/jsii-pacmak/lib/targets/java.ts index 64d76d0a5c..3f24d39b5e 100644 --- a/packages/jsii-pacmak/lib/targets/java.ts +++ b/packages/jsii-pacmak/lib/targets/java.ts @@ -2,6 +2,7 @@ import * as clone from 'clone'; import { toPascalCase } from 'codemaker/lib/case-utils'; import * as fs from 'fs-extra'; import * as reflect from 'jsii-reflect'; +import { Rosetta, typeScriptSnippetFromSource, Translation, markDownToJavaDoc } from 'jsii-rosetta'; import * as spec from '@jsii/spec'; import * as path from 'path'; import * as xmlbuilder from 'xmlbuilder'; @@ -9,10 +10,10 @@ import { Generator } from '../generator'; import { PackageInfo, Target, findLocalBuildDirs, TargetOptions } from '../target'; import * as logging from '../logging'; import { shell, Scratch, slugify, setExtend } from '../util'; -import { VERSION, VERSION_DESC } from '../version'; import { TargetBuilder, BuildOptions } from '../builder'; import { JsiiModule } from '../packaging'; -import { Rosetta, typeScriptSnippetFromSource, Translation, markDownToJavaDoc } from 'jsii-rosetta'; +import { VERSION, VERSION_DESC } from '../version'; +import { toMavenVersionRange } from './version-utils'; import { INCOMPLETE_DISCLAIMER_COMPILING, INCOMPLETE_DISCLAIMER_NONCOMPILING } from '.'; // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-require-imports @@ -429,7 +430,7 @@ class JavaGenerator extends Generator { * for example, we need to refer to their types when flatting the class hierarchy for * interface proxies. */ - private readonly referencedModules: { [name: string]: spec.PackageVersion } = { }; + private readonly referencedModules: { [name: string]: spec.AssemblyConfiguration } = { }; public constructor(private readonly rosetta: Rosetta) { super({ generateOverloadsForMethodWithOptionals: true }); @@ -777,13 +778,12 @@ class JavaGenerator extends Generator { this.code.closeFile('pom.xml'); /** - * Combines a version number with an optional suffix. If the suffix starts with '-' or '.', it will be - * concatenated as-is to the semantic version number. Otherwise, it'll be appended to the version number with an - * intercalar '-'. - * - * @param version the semantic version number - * @param suffix the suffix, if any. - */ + * Combines a version number with an optional suffix. The suffix, when present, must begin with + * '-' or '.', and will be concatenated as-is to the version number.. + * + * @param version the semantic version number + * @param suffix the suffix, if any. + */ function makeVersion(version: string, suffix?: string): string { if (!suffix) { return version; } if (!suffix.startsWith('-') && !suffix.startsWith('.')) { @@ -794,23 +794,22 @@ class JavaGenerator extends Generator { function mavenDependencies(this: JavaGenerator) { const dependencies = new Array(); - const allDeps = { ...assm.dependencies ?? {}, ...this.referencedModules }; - for (const depName of Object.keys(allDeps)) { - const dep = allDeps[depName]; - if (!dep.targets?.java) { + for (const [depName, version] of Object.entries(this.assembly.dependencies ?? {})) { + const dep = this.assembly.dependencyClosure?.[depName]; + if (!dep?.targets?.java) { throw new Error(`Assembly ${assm.name} depends on ${depName}, which does not declare a java target`); } dependencies.push({ groupId: dep.targets.java.maven.groupId, artifactId: dep.targets.java.maven.artifactId, - version: makeVersion(dep.version, dep.targets.java.maven.versionSuffix), + version: toMavenVersionRange(version, dep.targets.java.maven.versionSuffix), }); } // The JSII java runtime base classes dependencies.push({ groupId: 'software.amazon.jsii', artifactId: 'jsii-runtime', - version: VERSION + version: toMavenVersionRange(`^${VERSION}`) }); // Provides @javax.annotation.* @@ -1833,10 +1832,12 @@ class JavaGenerator extends Generator { } private getNativeName(assm: spec.Assembly, name: string | undefined): string; - private getNativeName(assm: spec.PackageVersion, name: string | undefined, assmName: string): string; - private getNativeName(assm: spec.Assembly | spec.PackageVersion, + private getNativeName(assm: spec.AssemblyConfiguration, name: string | undefined, assmName: string): string; + private getNativeName( + assm: spec.AssemblyConfiguration, name: string | undefined, - assmName: string = (assm as spec.Assembly).name): string { + assmName: string = (assm as spec.Assembly).name + ): string { const javaPackage = assm.targets?.java?.package; if (!javaPackage) { throw new Error(`The module ${assmName} does not have a java.package setting`); } return `${javaPackage}${name ? `.${name}` : ''}`; diff --git a/packages/jsii-pacmak/lib/targets/python.ts b/packages/jsii-pacmak/lib/targets/python.ts index a9b4047c1e..198c4cdacf 100644 --- a/packages/jsii-pacmak/lib/targets/python.ts +++ b/packages/jsii-pacmak/lib/targets/python.ts @@ -1,8 +1,7 @@ -import * as path from 'path'; - import { CodeMaker, toSnakeCase } from 'codemaker'; import * as escapeStringRegexp from 'escape-string-regexp'; import * as reflect from 'jsii-reflect'; +import * as path from 'path'; import * as spec from '@jsii/spec'; import { Stability } from '@jsii/spec'; import { Generator, GeneratorOptions } from '../generator'; @@ -11,6 +10,7 @@ import { md2rst } from '../markdown'; import { Target, TargetOptions } from '../target'; import { shell } from '../util'; import { Translation, Rosetta, typeScriptSnippetFromSource } from 'jsii-rosetta'; +import { toPythonVersionRange } from './version-utils'; import { INCOMPLETE_DISCLAIMER_COMPILING, INCOMPLETE_DISCLAIMER_NONCOMPILING } from '.'; @@ -1093,8 +1093,8 @@ class Module implements PythonType { private emitDependencyImports(code: CodeMaker, _resolver: TypeResolver) { const deps = Array.from( new Set([ - ...Object.values(this.assembly.dependencies ?? {}).map(d => { - return d.targets!.python!.module; + ...Object.keys(this.assembly.dependencies ?? {}).map(d => { + return this.assembly.dependencyClosure![d]!.targets!.python!.module; }), ]) ); @@ -1182,21 +1182,9 @@ class Package { // Compute our list of dependencies const dependencies: string[] = []; - const expectedDeps = this.metadata.dependencies ?? {}; - for (const depName of Object.keys(expectedDeps)) { - const depInfo = expectedDeps[depName]; - // We need to figure out what our version range is. - // Basically, if it starts with Zero we want to restrict things to - // ~=X.Y.Z. If it does not start with zero, then we want to do ~=X.Y,>=X.Y.Z. - const versionParts = depInfo.version.split('.'); - let versionSpecifier: string; - if (versionParts[0] === '0') { - versionSpecifier = `~=${versionParts.slice(0, 3).join('.')}`; - } else { - versionSpecifier = `~=${versionParts.slice(0, 2).join('.')},>=${versionParts.slice(0, 3).join('.')}`; - } - - dependencies.push(`${depInfo.targets!.python!.distName}${versionSpecifier}`); + for (const [depName, version] of Object.entries(this.metadata.dependencies ?? {})) { + const depInfo = this.metadata.dependencyClosure![depName]; + dependencies.push(`${depInfo.targets!.python!.distName}${toPythonVersionRange(version)}`); } code.openFile('README.md'); @@ -1292,7 +1280,7 @@ class Package { } } -type FindModuleCallback = (fqn: string) => spec.Assembly | spec.PackageVersion; +type FindModuleCallback = (fqn: string) => spec.AssemblyConfiguration; type FindTypeCallback = (fqn: string) => spec.Type; interface TypeResolverOpts { diff --git a/packages/jsii-pacmak/lib/targets/version-utils.ts b/packages/jsii-pacmak/lib/targets/version-utils.ts new file mode 100644 index 0000000000..41afa1db02 --- /dev/null +++ b/packages/jsii-pacmak/lib/targets/version-utils.ts @@ -0,0 +1,93 @@ +import { Comparator, Range } from 'semver'; + +/** + * Converts a SemVer range expression to a Maven version range expression. + * + * @param semverRange the SemVer range expression to convert. + * @param suffix the suffix to add to versions in the range. + * + * @see https://cwiki.apache.org/confluence/display/MAVENOLD/Dependency+Mediation+and+Conflict+Resolution + */ +export function toMavenVersionRange(semverRange: string, suffix?: string): string { + return toBracketNotation(semverRange, suffix); +} + +/** + * Converts a SemVer range expression to a NuGet version range expression. + * + * @param semverRange the SemVer range expression to convert. + * + * @see https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#version-ranges-and-wildcards + */ +export function toNuGetVersionRange(semverRange: string): string { + return toBracketNotation(semverRange); +} + +/** + * Converts a SemVer range expression to a Python setuptools compatible version + * constraint expression. + * + * @param semverRange the SemVer range expression to convert. + */ +export function toPythonVersionRange(semverRange: string): string { + const range = new Range(semverRange); + return range.set.map( + set => set.map( + comp => { + switch (comp.operator) { + case '': + case '=': + return `==${comp.semver.raw}`; + default: // >, >=, <, <= are all valid expressions + return `${comp.operator}${comp.semver.raw}`; + } + } + ).join(', ') + ).join(', '); +} + +function toBracketNotation(semverRange: string, suffix?: string): string { + const range = new Range(semverRange); + return range.set.map(set => { + if (set.length === 1) { + switch (set[0].operator || '=') { + // "[version]" => means exactly version + case '=': return `[${addSuffix(set[0].semver.raw)}]`; + // "(version,]" => means greater than version + case '>': return `(${addSuffix(set[0].semver.raw)},]`; + // "[version,]" => means greater than or equal to that version + case '>=': return `[${addSuffix(set[0].semver.raw)},]`; + // "[,version)" => means less than version + case '<': return `[,${addSuffix(set[0].semver.raw)})`; + // "[,version]" => means less than or equal to version + case '<=': return `[,${addSuffix(set[0].semver.raw)}]`; + } + } else if (set.length === 2) { + const nugetRange = toBracketRange(set[0], set[1]); + if (nugetRange) { + return nugetRange; + } + } + throw new Error(`Unsupported SemVer range set: ${set.map(comp => comp.value).join(', ')}`); + }).join(', '); + + function toBracketRange(left: Comparator, right: Comparator): string | undefined { + if (left.operator.startsWith('<') && right.operator.startsWith('>')) { + // Order isn't ideal, swap around.. + [left, right] = [right, left]; + } + + if (!left.operator.startsWith('>') || !right.operator.startsWith('<')) { + // We only support ranges defined like "> (or >=) left, < (or <=) right" + return undefined; + } + + const leftBrace = left.operator.endsWith('=') ? '[' : '('; + const rightBrace = right.operator.endsWith('=') ? ']' : ')'; + return `${leftBrace}${addSuffix(left.semver.raw)},${addSuffix(right.semver.raw)}${rightBrace}`; + } + + function addSuffix(str: string) { + return suffix ? `${str}${suffix}` : str; + } +} diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-base-of-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId/Amazon/JSII/Tests/CalculatorNamespace/BaseOfBaseNamespace/Internal/DependencyResolution/Anchor.cs b/packages/jsii-pacmak/test/expected.jsii-calc-base-of-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId/Amazon/JSII/Tests/CalculatorNamespace/BaseOfBaseNamespace/Internal/DependencyResolution/Anchor.cs new file mode 100644 index 0000000000..5cd3ec3cb0 --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc-base-of-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId/Amazon/JSII/Tests/CalculatorNamespace/BaseOfBaseNamespace/Internal/DependencyResolution/Anchor.cs @@ -0,0 +1,9 @@ +namespace Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace.Internal.DependencyResolution +{ + public sealed class Anchor + { + public Anchor() + { + } + } +} diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-base-of-base/java/pom.xml b/packages/jsii-pacmak/test/expected.jsii-calc-base-of-base/java/pom.xml index 8ec3c5795a..ae1ff0a024 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-base-of-base/java/pom.xml +++ b/packages/jsii-pacmak/test/expected.jsii-calc-base-of-base/java/pom.xml @@ -36,7 +36,7 @@ software.amazon.jsii jsii-runtime - 0.20.11 + [0.20.11,0.21.0) javax.annotation diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/.jsii b/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/.jsii index f947cca587..201ae10995 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/.jsii +++ b/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/.jsii @@ -8,29 +8,7 @@ "url": "https://aws.amazon.com" }, "dependencies": { - "@scope/jsii-calc-base-of-base": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId" - }, - "java": { - "maven": { - "artifactId": "calculator-base-of-base", - "groupId": "software.amazon.jsii.tests" - }, - "package": "software.amazon.jsii.tests.calculator.baseofbase" - }, - "js": { - "npm": "@scope/jsii-calc-base-of-base" - }, - "python": { - "distName": "scope.jsii-calc-base-of-base", - "module": "scope.jsii_calc_base_of_base" - } - }, - "version": "0.20.11" - } + "@scope/jsii-calc-base-of-base": "0.20.11" }, "dependencyClosure": { "@scope/jsii-calc-base-of-base": { @@ -53,8 +31,7 @@ "distName": "scope.jsii-calc-base-of-base", "module": "scope.jsii_calc_base_of_base" } - }, - "version": "0.20.11" + } } }, "description": "An example direct dependency for jsii-calc.", @@ -174,5 +151,5 @@ } }, "version": "0.20.11", - "fingerprint": "lSQlk5mMPd7fxaZoCdCekSFY4rDOAu3VnLuIcFUXA6o=" + "fingerprint": "vaUHiWCfpSsCfXz18WPVQ3HhCBTPj23pDky4IqmEWxw=" } diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId.csproj b/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId.csproj index eca51885aa..4e409860ad 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId.csproj +++ b/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId.csproj @@ -25,7 +25,7 @@ - + 0612,0618 diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/Amazon/JSII/Tests/CalculatorNamespace/BaseNamespace/Internal/DependencyResolution/Anchor.cs b/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/Amazon/JSII/Tests/CalculatorNamespace/BaseNamespace/Internal/DependencyResolution/Anchor.cs new file mode 100644 index 0000000000..4a5728b15d --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/Amazon/JSII/Tests/CalculatorNamespace/BaseNamespace/Internal/DependencyResolution/Anchor.cs @@ -0,0 +1,10 @@ +namespace Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution +{ + public sealed class Anchor + { + public Anchor() + { + new Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace.Internal.DependencyResolution.Anchor(); + } + } +} diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-base/java/pom.xml b/packages/jsii-pacmak/test/expected.jsii-calc-base/java/pom.xml index 53f9bfcda7..144d30b73d 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-base/java/pom.xml +++ b/packages/jsii-pacmak/test/expected.jsii-calc-base/java/pom.xml @@ -36,12 +36,12 @@ software.amazon.jsii.tests calculator-base-of-base - 0.20.11 + [0.20.11] software.amazon.jsii jsii-runtime - 0.20.11 + [0.20.11,0.21.0) javax.annotation diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-base/python/setup.py b/packages/jsii-pacmak/test/expected.jsii-calc-base/python/setup.py index 960636132a..d76b93338a 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-base/python/setup.py +++ b/packages/jsii-pacmak/test/expected.jsii-calc-base/python/setup.py @@ -32,7 +32,7 @@ "install_requires": [ "jsii~=0.20.11", "publication>=0.0.3", - "scope.jsii-calc-base-of-base~=0.20.11" + "scope.jsii-calc-base-of-base==0.20.11" ], "classifiers": [ "Intended Audience :: Developers", diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/.jsii b/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/.jsii index aee0bcedeb..2aa69c9263 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/.jsii +++ b/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/.jsii @@ -8,29 +8,7 @@ "url": "https://aws.amazon.com" }, "dependencies": { - "@scope/jsii-calc-base": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BasePackageId" - }, - "java": { - "maven": { - "artifactId": "calculator-base", - "groupId": "software.amazon.jsii.tests" - }, - "package": "software.amazon.jsii.tests.calculator.base" - }, - "js": { - "npm": "@scope/jsii-calc-base" - }, - "python": { - "distName": "scope.jsii-calc-base", - "module": "scope.jsii_calc_base" - } - }, - "version": "0.20.11" - } + "@scope/jsii-calc-base": "^0.20.11" }, "dependencyClosure": { "@scope/jsii-calc-base": { @@ -53,8 +31,7 @@ "distName": "scope.jsii-calc-base", "module": "scope.jsii_calc_base" } - }, - "version": "0.20.11" + } }, "@scope/jsii-calc-base-of-base": { "targets": { @@ -76,8 +53,7 @@ "distName": "scope.jsii-calc-base-of-base", "module": "scope.jsii_calc_base_of_base" } - }, - "version": "0.20.11" + } } }, "description": "A simple calcuator library built on JSII.", @@ -540,5 +516,5 @@ } }, "version": "0.20.11", - "fingerprint": "WSHD7tywHgFC9jmImvDuy7NGYCDWAAb49jk0ZPzCoFM=" + "fingerprint": "tdRWsMWbPQxej79pQ39gbrrViv/Wl6vfTLY59IJPi4w=" } diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId.csproj b/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId.csproj index 39ad0bcf10..2275a0ab28 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId.csproj +++ b/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId.csproj @@ -25,7 +25,7 @@ - + 0612,0618 diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/Amazon/JSII/Tests/CalculatorNamespace/LibNamespace/Internal/DependencyResolution/Anchor.cs b/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/Amazon/JSII/Tests/CalculatorNamespace/LibNamespace/Internal/DependencyResolution/Anchor.cs new file mode 100644 index 0000000000..19be6786f5 --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/Amazon/JSII/Tests/CalculatorNamespace/LibNamespace/Internal/DependencyResolution/Anchor.cs @@ -0,0 +1,10 @@ +namespace Amazon.JSII.Tests.CalculatorNamespace.LibNamespace.Internal.DependencyResolution +{ + public sealed class Anchor + { + public Anchor() + { + new Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution.Anchor(); + } + } +} diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-lib/java/pom.xml b/packages/jsii-pacmak/test/expected.jsii-calc-lib/java/pom.xml index 2a7c3608d6..dd403ce6a8 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-lib/java/pom.xml +++ b/packages/jsii-pacmak/test/expected.jsii-calc-lib/java/pom.xml @@ -36,12 +36,12 @@ software.amazon.jsii.tests calculator-base - 0.20.11 + [0.20.11,0.21.0) software.amazon.jsii jsii-runtime - 0.20.11 + [0.20.11,0.21.0) javax.annotation diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-lib/python/setup.py b/packages/jsii-pacmak/test/expected.jsii-calc-lib/python/setup.py index fad4ca0394..3588e20486 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-lib/python/setup.py +++ b/packages/jsii-pacmak/test/expected.jsii-calc-lib/python/setup.py @@ -32,7 +32,7 @@ "install_requires": [ "jsii~=0.20.11", "publication>=0.0.3", - "scope.jsii-calc-base~=0.20.11" + "scope.jsii-calc-base>=0.20.11, <0.21.0" ], "classifiers": [ "Intended Audience :: Developers", diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii index 841f093220..14afe903a7 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii +++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii @@ -34,77 +34,9 @@ } ], "dependencies": { - "@scope/jsii-calc-base": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BasePackageId" - }, - "java": { - "maven": { - "artifactId": "calculator-base", - "groupId": "software.amazon.jsii.tests" - }, - "package": "software.amazon.jsii.tests.calculator.base" - }, - "js": { - "npm": "@scope/jsii-calc-base" - }, - "python": { - "distName": "scope.jsii-calc-base", - "module": "scope.jsii_calc_base" - } - }, - "version": "0.20.11" - }, - "@scope/jsii-calc-base-of-base": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId" - }, - "java": { - "maven": { - "artifactId": "calculator-base-of-base", - "groupId": "software.amazon.jsii.tests" - }, - "package": "software.amazon.jsii.tests.calculator.baseofbase" - }, - "js": { - "npm": "@scope/jsii-calc-base-of-base" - }, - "python": { - "distName": "scope.jsii-calc-base-of-base", - "module": "scope.jsii_calc_base_of_base" - } - }, - "version": "0.20.11" - }, - "@scope/jsii-calc-lib": { - "targets": { - "dotnet": { - "namespace": "Amazon.JSII.Tests.CalculatorNamespace.LibNamespace", - "packageId": "Amazon.JSII.Tests.CalculatorPackageId.LibPackageId", - "versionSuffix": "-devpreview" - }, - "java": { - "maven": { - "artifactId": "calculator-lib", - "groupId": "software.amazon.jsii.tests", - "versionSuffix": ".DEVPREVIEW" - }, - "package": "software.amazon.jsii.tests.calculator.lib" - }, - "js": { - "npm": "@scope/jsii-calc-lib" - }, - "python": { - "distName": "scope.jsii-calc-lib", - "module": "scope.jsii_calc_lib" - } - }, - "version": "0.20.11" - } + "@scope/jsii-calc-base": "^0.20.11", + "@scope/jsii-calc-base-of-base": "^0.20.11", + "@scope/jsii-calc-lib": "^0.20.11" }, "dependencyClosure": { "@scope/jsii-calc-base": { @@ -127,8 +59,7 @@ "distName": "scope.jsii-calc-base", "module": "scope.jsii_calc_base" } - }, - "version": "0.20.11" + } }, "@scope/jsii-calc-base-of-base": { "targets": { @@ -150,8 +81,7 @@ "distName": "scope.jsii-calc-base-of-base", "module": "scope.jsii_calc_base_of_base" } - }, - "version": "0.20.11" + } }, "@scope/jsii-calc-lib": { "targets": { @@ -175,8 +105,7 @@ "distName": "scope.jsii-calc-lib", "module": "scope.jsii_calc_lib" } - }, - "version": "0.20.11" + } } }, "description": "A simple calcuator built on JSII.", @@ -12093,5 +12022,5 @@ } }, "version": "0.20.11", - "fingerprint": "5M7u8+bWxu4amY/gs4Z/K4IpYqJCMGtnQiVoerS4uac=" + "fingerprint": "j7Hsf1Hd/mih3Gr3UR7vYcXeLrbixxffBTzvSokikjM=" } diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon.JSII.Tests.CalculatorPackageId.csproj b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon.JSII.Tests.CalculatorPackageId.csproj index 1a901072ec..50cc042d87 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon.JSII.Tests.CalculatorPackageId.csproj +++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon.JSII.Tests.CalculatorPackageId.csproj @@ -27,9 +27,9 @@ - - - + + + 0612,0618 diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/Internal/DependencyResolution/Anchor.cs b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/Internal/DependencyResolution/Anchor.cs new file mode 100644 index 0000000000..b446d0e4ed --- /dev/null +++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/Amazon/JSII/Tests/CalculatorNamespace/Internal/DependencyResolution/Anchor.cs @@ -0,0 +1,12 @@ +namespace Amazon.JSII.Tests.CalculatorNamespace.Internal.DependencyResolution +{ + public sealed class Anchor + { + public Anchor() + { + new Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace.Internal.DependencyResolution.Anchor(); + new Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace.Internal.DependencyResolution.Anchor(); + new Amazon.JSII.Tests.CalculatorNamespace.LibNamespace.Internal.DependencyResolution.Anchor(); + } + } +} diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/java/pom.xml b/packages/jsii-pacmak/test/expected.jsii-calc/java/pom.xml index d91eb3248b..e7c101d5c8 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/java/pom.xml +++ b/packages/jsii-pacmak/test/expected.jsii-calc/java/pom.xml @@ -57,22 +57,22 @@ software.amazon.jsii.tests calculator-base - 0.20.11 + [0.20.11,0.21.0) software.amazon.jsii.tests calculator-base-of-base - 0.20.11 + [0.20.11,0.21.0) software.amazon.jsii.tests calculator-lib - 0.20.11.DEVPREVIEW + [0.20.11.DEVPREVIEW,0.21.0.DEVPREVIEW) software.amazon.jsii jsii-runtime - 0.20.11 + [0.20.11,0.21.0) javax.annotation diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/python/setup.py b/packages/jsii-pacmak/test/expected.jsii-calc/python/setup.py index 4b0431ce97..eb7056b80a 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/python/setup.py +++ b/packages/jsii-pacmak/test/expected.jsii-calc/python/setup.py @@ -32,9 +32,9 @@ "install_requires": [ "jsii~=0.20.11", "publication>=0.0.3", - "scope.jsii-calc-base~=0.20.11", - "scope.jsii-calc-base-of-base~=0.20.11", - "scope.jsii-calc-lib~=0.20.11" + "scope.jsii-calc-base>=0.20.11, <0.21.0", + "scope.jsii-calc-base-of-base>=0.20.11, <0.21.0", + "scope.jsii-calc-lib>=0.20.11, <0.21.0" ], "classifiers": [ "Intended Audience :: Developers", diff --git a/packages/jsii-pacmak/test/targets/version-utils.test.ts b/packages/jsii-pacmak/test/targets/version-utils.test.ts new file mode 100644 index 0000000000..9b227c7303 --- /dev/null +++ b/packages/jsii-pacmak/test/targets/version-utils.test.ts @@ -0,0 +1,77 @@ +import { toMavenVersionRange, toNuGetVersionRange, toPythonVersionRange } from '../../lib/targets/version-utils'; + +const examples: Record = { + // Regular versions, "classic" ranges + '1.2.3': { + maven: '[1.2.3]', + nuget: '[1.2.3]', + python: '==1.2.3', + }, + '~1.2.3': { + maven: '[1.2.3,1.3.0)', + nuget: '[1.2.3,1.3.0)', + python: '>=1.2.3, <1.3.0', + }, + '^1.2.3': { + maven: '[1.2.3,2.0.0)', + nuget: '[1.2.3,2.0.0)', + python: '>=1.2.3, <2.0.0', + }, + + // Pre-1.0 versions, "classic" ranges + '0.1.2': { + maven: '[0.1.2]', + nuget: '[0.1.2]', + python: '==0.1.2', + }, + '~0.1.2': { + maven: '[0.1.2,0.2.0)', + nuget: '[0.1.2,0.2.0)', + python: '>=0.1.2, <0.2.0', + }, + '^0.1.2': { + maven: '[0.1.2,0.2.0)', + nuget: '[0.1.2,0.2.0)', + python: '>=0.1.2, <0.2.0', + }, + + // Less usual ranges + '>0.1.2': { + maven: '(0.1.2,]', + nuget: '(0.1.2,]', + python: '>0.1.2', + }, + '>=0.1.2': { + maven: '[0.1.2,]', + nuget: '[0.1.2,]', + python: '>=0.1.2', + }, + '<0.1.2': { + maven: '[,0.1.2)', + nuget: '[,0.1.2)', + python: '<0.1.2', + }, + '<=0.1.2': { + maven: '[,0.1.2]', + nuget: '[,0.1.2]', + python: '<=0.1.2', + }, +}; + +describe(toMavenVersionRange, () => { + for (const [semver, { maven }] of Object.entries(examples)) { + test(`${semver} translates to ${maven}`, () => expect(toMavenVersionRange(semver)).toEqual(maven)); + } +}); + +describe(toNuGetVersionRange, () => { + for (const [semver, { nuget }] of Object.entries(examples)) { + test(`${semver} translates to ${nuget}`, () => expect(toNuGetVersionRange(semver)).toEqual(nuget)); + } +}); + +describe(toPythonVersionRange, () => { + for (const [semver, { python }] of Object.entries(examples)) { + test(`${semver} translates to ${python}`, () => expect(toPythonVersionRange(semver)).toEqual(python)); + } +}); diff --git a/packages/jsii-reflect/lib/dependency.ts b/packages/jsii-reflect/lib/dependency.ts index 6a6a5c66a1..769e36fc43 100644 --- a/packages/jsii-reflect/lib/dependency.ts +++ b/packages/jsii-reflect/lib/dependency.ts @@ -1,21 +1,13 @@ -import * as jsii from '@jsii/spec'; import { TypeSystem } from './type-system'; export class Dependency { public constructor( public readonly system: TypeSystem, private readonly name: string, - public readonly spec: jsii.PackageVersion + public readonly version: string ) { } public get assembly() { return this.system.findAssembly(this.name); } - - /** - * Version of the package. - */ - public get version() { - return this.spec.version; - } } diff --git a/packages/jsii/lib/assembler.ts b/packages/jsii/lib/assembler.ts index 67facf3344..eeec9162bd 100644 --- a/packages/jsii/lib/assembler.ts +++ b/packages/jsii/lib/assembler.ts @@ -6,7 +6,6 @@ import * as fs from 'fs-extra'; import * as spec from '@jsii/spec'; import * as log4js from 'log4js'; import * as path from 'path'; -import * as semver from 'semver'; import * as ts from 'typescript'; import { JSII_DIAGNOSTICS_CODE } from './compiler'; import { getReferencedDocParams, parseSymbolDocumentation } from './docs'; @@ -115,8 +114,8 @@ export class Assembler implements Emitter { author: this.projectInfo.author, contributors: this.projectInfo.contributors && [...this.projectInfo.contributors], repository: this.projectInfo.repository, - dependencies: this._toDependencies(this.projectInfo.dependencies), - dependencyClosure: this._buildDependencyClosure(this.projectInfo.dependencies), + dependencies: noEmptyDict({ ...this.projectInfo.dependencies, ...this.projectInfo.peerDependencies }), + dependencyClosure: noEmptyDict(toDependencyClosure(this.projectInfo.dependencyClosure)), bundled: this.projectInfo.bundleDependencies, types: this._types, targets: this.projectInfo.targets, @@ -234,14 +233,13 @@ export class Assembler implements Emitter { if (assm === this.projectInfo.name) { type = this._types[ref]; } else { - const assembly = this.projectInfo.transitiveDependencies.find(dep => dep.name === assm); + const assembly = this.projectInfo.dependencyClosure.find(dep => dep.name === assm); type = assembly?.types?.[ref]; // since we are exposing a type of this assembly in this module's public API, // we expect it to appear as a peer dependency instead of a normal dependency. if (assembly) { - const asPeerDependency = this.projectInfo.peerDependencies.find(d => d.name === assembly.name); - if (!asPeerDependency) { + if (!(assembly.name in this.projectInfo.peerDependencies)) { this._diagnostic(referencingNode, ts.DiagnosticCategory.Warning, `The type '${ref}' is exposed in the public API of this module. ` + `Therefore, the module '${assembly.name}' must also be defined under "peerDependencies". ` + @@ -1341,71 +1339,6 @@ export class Assembler implements Emitter { } } } - - private _toDependencies(assemblies: readonly spec.Assembly[]): { [name: string]: spec.PackageVersion } | undefined { - const ret: { [name: string]: spec.PackageVersion } = {}; - - for (const a of assemblies) { - Object.assign(ret, assemblyToPackageVersionMap(a)); - } - - return noEmptyDict(ret); - } - - private _buildDependencyClosure(assemblies: readonly spec.Assembly[]): { [name: string]: spec.PackageVersion } | undefined { - // Merge the dependency closures of all dependencies and add the direct dependencies. - // There should not be version conflicts between them but we guard against it anyway. - - // Get an array of dependency maps - const dependencyBags = flatten(assemblies.map(a => [assemblyToPackageVersionMap(a), a.dependencies ?? {}])); - - const warned = new Set(); - const result: { [name: string]: spec.PackageVersion } = {}; - for (const bag of dependencyBags) { - for (const [name, packV] of Object.entries(bag)) { - maybeRecord.call(this, name, packV); - } - } - - return noEmptyDict(result); - - function maybeRecord(this: Assembler, name: string, pack: spec.PackageVersion) { - let recordThisDependency = true; - - if (name in result) { - // Two dependencies on the same package, find the right version to use - const highestVersion = mostConstrainedVersion(result[name].version, pack.version); - - if (highestVersion === undefined) { - warnAboutVersionConflict.call(this, name, result[name].version, pack.version); - } - - recordThisDependency = pack.version === highestVersion; - } - - if (recordThisDependency) { - result[name] = { - version: pack.version, - targets: pack.targets, - }; - } - } - - function warnAboutVersionConflict(this: Assembler, name: string, v1: string, v2: string) { - if (warned.has(name)) { return; } - this._diagnostic(null, ts.DiagnosticCategory.Error, `Conflicting dependencies on incompatible versions for package '${name}': ${v1} and ${v2}`); - warned.add(name); - } - } -} - -function assemblyToPackageVersionMap(a: spec.Assembly): {[key: string]: spec.PackageVersion} { - return { - [a.name]: { - version: a.version, - targets: a.targets - } - }; } function _fingerprint(assembly: spec.Assembly): spec.Assembly { @@ -1624,35 +1557,20 @@ function* intersect(xs: Set, ys: Set) { } } -/** - * Return the most constrained version given two versions - * - * Returns the highest version. Return undefined if the values are not pairwise - * comparable. - */ -function mostConstrainedVersion(version1: string, version2: string): string | undefined { - if (semver.satisfies(version1, `^${version2}`)) { - // If v1 satisifies v2, then either: - // - v2 also satisfies v1, in which case it doesn't matter which we return - // - v2 does not satisfy v1, in which it must be the case that v1 is higher - return version1; - } - - // Reverse logic - if (semver.satisfies(version2, `^${version1}`)) { return version2; } - - return undefined; -} - -function flatten(xs: T[][]): T[] { - return Array.prototype.concat.call([], ...xs); -} - function noEmptyDict(xs: {[key: string]: T}): {[key: string]: T} | undefined { if (Object.keys(xs).length === 0) { return undefined; } return xs; } +function toDependencyClosure(assemblies: readonly spec.Assembly[]): { [name: string]: spec.AssemblyConfiguration } { + const result: { [name: string]: spec.AssemblyTargets } = {}; + for (const assembly of assemblies) { + if (!assembly.targets) { continue; } + result[assembly.name] = { targets: assembly.targets }; + } + return result; +} + /** * Check whether this type is the intrinsic TypeScript "error type" * diff --git a/packages/jsii/lib/project-info.ts b/packages/jsii/lib/project-info.ts index 864457aab6..1fb1b894d6 100644 --- a/packages/jsii/lib/project-info.ts +++ b/packages/jsii/lib/project-info.ts @@ -3,6 +3,7 @@ import * as spec from '@jsii/spec'; import * as log4js from 'log4js'; import * as path from 'path'; import * as semver from 'semver'; +import { intersect } from 'semver-intersect'; import { parsePerson, parseRepository } from './utils'; // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports @@ -35,9 +36,9 @@ export interface ProjectInfo { readonly main: string; readonly types: string; - readonly dependencies: readonly spec.Assembly[]; - readonly peerDependencies: readonly spec.Assembly[]; - readonly transitiveDependencies: readonly spec.Assembly[]; + readonly dependencies: { readonly [name: string]: string }; + readonly peerDependencies: { readonly [name: string]: string }; + readonly dependencyClosure: readonly spec.Assembly[]; readonly bundleDependencies?: { readonly [name: string]: string }; readonly targets: spec.AssemblyTargets; readonly metadata?: { [key: string]: any }; @@ -107,7 +108,7 @@ export async function loadProjectInfo(projectRoot: string, { fixPeerDependencies const peerDependencies = await _loadDependencies(pkg.peerDependencies, projectRoot, transitiveAssemblies); - const transitiveDependencies = Object.keys(transitiveAssemblies).map(name => transitiveAssemblies[name]); + const transitiveDependencies = Object.values(transitiveAssemblies); return { projectRoot, @@ -127,7 +128,7 @@ export async function loadProjectInfo(projectRoot: string, { fixPeerDependencies dependencies, peerDependencies, - transitiveDependencies, + dependencyClosure: transitiveDependencies, bundleDependencies, targets: { ..._required(pkg.jsii, 'The "package.json" file must specify the "jsii" attribute').targets, @@ -158,12 +159,14 @@ function _guessRepositoryType(url: string): string { throw new Error(`The "package.json" file must specify the "repository.type" attribute (could not guess from ${url})`); } -async function _loadDependencies(dependencies: { [name: string]: string | spec.PackageVersion } | undefined, +async function _loadDependencies( + dependencies: { [name: string]: string } | undefined, searchPath: string, transitiveAssemblies: { [name: string]: spec.Assembly }, - bundled = new Set()): Promise { - if (!dependencies) { return []; } - const assemblies = new Array(); + bundled = new Set() +): Promise<{ [name: string]: string }> { + if (!dependencies) { return {}; } + const packageVersions: { [name: string]: string } = {}; for (const name of Object.keys(dependencies)) { if (bundled.has(name)) { continue; } const { version: versionString, localPackage } = _resolveVersion(dependencies[name], searchPath); @@ -178,7 +181,9 @@ async function _loadDependencies(dependencies: { [name: string]: string | spec.P if (!version.intersects(new semver.Range(assm.version))) { throw new Error(`Declared dependency on version ${versionString} of ${name}, but version ${assm.version} was found`); } - assemblies.push(assm); + packageVersions[assm.name] = packageVersions[assm.name] != null + ? intersect(versionString!, packageVersions[assm.name]) + : versionString!; transitiveAssemblies[assm.name] = assm; const pkgDir = path.dirname(pkg); if (assm.dependencies) { @@ -186,7 +191,7 @@ async function _loadDependencies(dependencies: { [name: string]: string | spec.P await _loadDependencies(assm.dependencies, pkgDir, transitiveAssemblies); } } - return assemblies; + return packageVersions; } const ASSEMBLY_CACHE = new Map(); @@ -278,11 +283,7 @@ function _validateStability(stability: string | undefined, deprecated: string | return stability as spec.Stability; } -function _resolveVersion(dep: spec.PackageVersion | string | undefined, searchPath: string): { version: string | undefined, localPackage?: string } { - if (typeof dep !== 'string') { - return { version: dep?.version }; - } - +function _resolveVersion(dep: string, searchPath: string): { version: string | undefined, localPackage?: string } { const matches = /^file:(.+)$/.exec(dep); if (!matches) { return { version: dep }; diff --git a/packages/jsii/lib/validator.ts b/packages/jsii/lib/validator.ts index 7eb2dc29d8..f29c25b386 100644 --- a/packages/jsii/lib/validator.ts +++ b/packages/jsii/lib/validator.ts @@ -130,7 +130,7 @@ function _defaultValidations(): ValidationFunction[] { } continue; } - const foreignAssm = validator.projectInfo.transitiveDependencies.find(dep => dep.name === assm); + const foreignAssm = validator.projectInfo.dependencyClosure.find(dep => dep.name === assm); if (!foreignAssm) { diagnostic(ts.DiagnosticCategory.Error, `Type reference is rooted in unknown module: ${assm}`); @@ -362,7 +362,7 @@ function _dereference(typeRef: string | spec.NamedTypeReference, assembly: spec. if (assembly.name === assm) { return assembly.types?.[typeRef]; } - const foreignAssm = validator.projectInfo.transitiveDependencies.find(dep => dep.name === assm); + const foreignAssm = validator.projectInfo.dependencyClosure.find(dep => dep.name === assm); return foreignAssm?.types?.[typeRef]; } diff --git a/packages/jsii/package.json b/packages/jsii/package.json index eac0430c64..5cd9aaa34d 100644 --- a/packages/jsii/package.json +++ b/packages/jsii/package.json @@ -40,6 +40,7 @@ "fs-extra": "^8.1.0", "log4js": "^6.1.0", "semver": "^7.1.1", + "semver-intersect": "^1.4.0", "sort-json": "^2.0.0", "spdx-license-list": "^6.1.0", "typescript": "~3.7.4", diff --git a/packages/jsii/test/negatives.test.ts b/packages/jsii/test/negatives.test.ts index 45b70c46f1..9a33f8bbfe 100644 --- a/packages/jsii/test/negatives.test.ts +++ b/packages/jsii/test/negatives.test.ts @@ -74,9 +74,9 @@ function _makeProjectInfo(types: string): ProjectInfo { license: 'Apache-2.0', author: { name: 'John Doe', roles: ['author'] }, repository: { type: 'git', url: 'https://github.com/aws/jsii.git' }, - dependencies: [], - peerDependencies: [], - transitiveDependencies: [], + dependencies: {}, + peerDependencies: {}, + dependencyClosure: [], bundleDependencies: {}, targets: {}, excludeTypescript: [], diff --git a/packages/jsii/test/project-info.test.ts b/packages/jsii/test/project-info.test.ts index 45a0787684..44bbe2d8dd 100644 --- a/packages/jsii/test/project-info.test.ts +++ b/packages/jsii/test/project-info.test.ts @@ -18,8 +18,8 @@ const BASE_PROJECT = { jsii: { targets: { foo: { bar: 'baz' } } }, - dependencies: { 'jsii-test-dep': '^1.2.3' }, - peerDependencies: { 'jsii-test-dep': '^1.2.3' } + dependencies: { 'jsii-test-dep': '^1.2.3' } as { [name: string]: string }, + peerDependencies: { 'jsii-test-dep': '^1.2.3' } as { [name: string]: string } }; describe('loadProjectInfo', () => { @@ -36,8 +36,8 @@ describe('loadProjectInfo', () => { expect(info.repository?.type).toBe('git'); expect(info.repository?.url).toBe(BASE_PROJECT.repository.url); expect(info.targets).toEqual({ ...BASE_PROJECT.jsii.targets, js: { npm: BASE_PROJECT.name } }); - expect(info.dependencies).toEqual([TEST_DEP_ASSEMBLY]); - expect(info.transitiveDependencies).toEqual([TEST_DEP_ASSEMBLY, TEST_DEP_DEP_ASSEMBLY]); + expect(info.dependencies).toEqual({ [TEST_DEP_ASSEMBLY.name]: BASE_PROJECT.dependencies[TEST_DEP_ASSEMBLY.name] }); + expect(info.dependencyClosure).toEqual([TEST_DEP_ASSEMBLY, TEST_DEP_DEP_ASSEMBLY]); })); test('loads valid project (UNLICENSED)', () => _withTestProject(async projectRoot => { @@ -148,9 +148,7 @@ const TEST_DEP_ASSEMBLY: spec.Assembly = { author: { name: 'Amazon Web Services', url: 'https://aws.amazon.com', organization: true, roles: ['author'] }, fingerprint: 'F1NG3RPR1N7', dependencies: { - 'jsii-test-dep-dep': { - version: '3.2.1', - } + 'jsii-test-dep-dep': '3.2.1', }, jsiiVersion: VERSION, }; diff --git a/packages/jsii/vendor/.gitignore b/packages/jsii/vendor/.gitignore new file mode 100644 index 0000000000..f955bf36ab --- /dev/null +++ b/packages/jsii/vendor/.gitignore @@ -0,0 +1 @@ +!*.d.ts diff --git a/packages/jsii/vendor/semver-intersect.d.ts b/packages/jsii/vendor/semver-intersect.d.ts new file mode 100644 index 0000000000..c7f919bcf7 --- /dev/null +++ b/packages/jsii/vendor/semver-intersect.d.ts @@ -0,0 +1,13 @@ +/// Hand-written declaration for the semver-intersect module +declare module 'semver-intersect' { + /** + * Computes the intersection between multiple semver ranges. + * + * @param ranges the ranges for which the intersection is requested. + * + * @returns the intersection of `ranges`. + * + * @throws Error if the intersection is empty. + */ + function intersect(...ranges: string[]): string; +} diff --git a/yarn.lock b/yarn.lock index f7157bdedb..6f4ed898d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7140,7 +7140,14 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: +semver-intersect@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/semver-intersect/-/semver-intersect-1.4.0.tgz#bdd9c06bedcdd2fedb8cd352c3c43ee8c61321f3" + integrity sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ== + dependencies: + semver "^5.0.0" + +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==