diff --git a/packages/@aws-cdk/core/lib/annotations.ts b/packages/@aws-cdk/core/lib/annotations.ts index 1a6f900f4799a..4907f0b7987b0 100644 --- a/packages/@aws-cdk/core/lib/annotations.ts +++ b/packages/@aws-cdk/core/lib/annotations.ts @@ -1,8 +1,6 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; -import { IConstruct, Node } from 'constructs'; - -const DEPRECATIONS_SYMBOL = Symbol.for('@aws-cdk/core.deprecations'); +import { IConstruct } from 'constructs'; /** * Includes API for attaching annotations such as warning messages to constructs. @@ -77,38 +75,23 @@ export class Annotations { // throw if CDK_BLOCK_DEPRECATIONS is set if (process.env.CDK_BLOCK_DEPRECATIONS) { - throw new Error(`${Node.of(this.scope).path}: ${text}`); - } - - // de-dup based on api key - const set = this.deprecationsReported; - if (set.has(api)) { - return; + throw new Error(`${this.scope.node.path}: ${text}`); } this.addWarning(text); - set.add(api); } /** * Adds a message metadata entry to the construct node, to be displayed by the CDK CLI. + * + * Records the message once per construct. * @param level The message level * @param message The message itself */ private addMessage(level: string, message: string) { - Node.of(this.scope).addMetadata(level, message, { stackTrace: this.stackTraces }); - } - - /** - * Returns the set of deprecations reported on this construct. - */ - private get deprecationsReported() { - let set = (this.scope as any)[DEPRECATIONS_SYMBOL]; - if (!set) { - set = new Set(); - Object.defineProperty(this.scope, DEPRECATIONS_SYMBOL, { value: set }); + const isNew = !this.scope.node.metadata.find((x) => x.data === message); + if (isNew) { + this.scope.node.addMetadata(level, message, { stackTrace: this.stackTraces }); } - - return set; } } diff --git a/packages/@aws-cdk/core/test/annotations.test.ts b/packages/@aws-cdk/core/test/annotations.test.ts index 2f8fd0eef12a1..fc5c7430d22a8 100644 --- a/packages/@aws-cdk/core/test/annotations.test.ts +++ b/packages/@aws-cdk/core/test/annotations.test.ts @@ -1,7 +1,7 @@ import { Construct } from 'constructs'; +import { getWarnings } from './util'; import { App, Stack } from '../lib'; import { Annotations } from '../lib/annotations'; -import { getWarnings } from './util'; const restore = process.env.CDK_BLOCK_DEPRECATIONS; @@ -74,4 +74,23 @@ describe('annotations', () => { process.env.CDK_BLOCK_DEPRECATIONS = '1'; expect(() => Annotations.of(c1).addDeprecation('foo', 'bar')).toThrow(/MyStack\/Hello: The API foo is deprecated: bar\. This API will be removed in the next major release/); }); + + test('addMessage deduplicates the message on the node level', () => { + const app = new App(); + const stack = new Stack(app, 'S1'); + const c1 = new Construct(stack, 'C1'); + Annotations.of(c1).addWarning('You should know this!'); + Annotations.of(c1).addWarning('You should know this!'); + Annotations.of(c1).addWarning('You should know this!'); + Annotations.of(c1).addWarning('You should know this, too!'); + expect(getWarnings(app.synth())).toEqual([{ + path: '/S1/C1', + message: 'You should know this!', + }, + { + path: '/S1/C1', + message: 'You should know this, too!', + }], + ); + }); }); diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts index ef94a85989f73..0f6997e392b18 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts @@ -320,6 +320,7 @@ export class CodePipeline extends PipelineBase { private _pipeline?: cp.Pipeline; private artifacts = new ArtifactMap(); private _synthProject?: cb.IProject; + private _selfMutationProject?: cb.IProject; private readonly selfMutation: boolean; private readonly useChangeSets: boolean; private _myCxAsmRoot?: string; @@ -365,6 +366,22 @@ export class CodePipeline extends PipelineBase { return this._synthProject; } + /** + * The CodeBuild project that performs the SelfMutation + * + * Will throw an error if this is accessed before `buildPipeline()` + * is called. + * + * May return no value if `selfMutation` was set to `false` when + * the `CodePipeline` was defined. + */ + public get selfMutationProject(): cb.IProject | undefined { + if (!this._pipeline) { + throw new Error('Call pipeline.buildPipeline() before reading this property'); + } + return this._selfMutationProject; + } + /** * The CodePipeline pipeline that deploys the CDK app * @@ -527,6 +544,9 @@ export class CodePipeline extends PipelineBase { if (nodeType === CodeBuildProjectType.SYNTH) { this._synthProject = result.project; } + if (nodeType === CodeBuildProjectType.SELF_MUTATE) { + this._selfMutationProject = result.project; + } } if (node.data?.type === 'step' && node.data.step.primaryOutput?.primaryOutput && !this._fallbackArtifact) { diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts index eb1fe053ef201..a0ea38fe6f264 100644 --- a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts @@ -8,7 +8,7 @@ import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as cdkp from '../../lib'; import { CodePipeline } from '../../lib'; -import { PIPELINE_ENV, TestApp, ModernTestGitHubNpmPipeline, FileAssetApp, TwoStackApp } from '../testhelpers'; +import { PIPELINE_ENV, TestApp, ModernTestGitHubNpmPipeline, FileAssetApp, TwoStackApp, StageWithStackOutput } from '../testhelpers'; let app: TestApp; @@ -426,6 +426,34 @@ test('synths with change set approvers', () => { }); }); +test('selfMutationProject can be accessed after buildPipeline', () => { + // GIVEN + const pipelineStack = new cdk.Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk'); + pipeline.addStage(new StageWithStackOutput(pipelineStack, 'Stage')); + + // WHEN + pipeline.buildPipeline(); + + // THEN + expect(pipeline.selfMutationProject).toBeTruthy(); +}); + +test('selfMutationProject is undefined if switched off', () => { + // GIVEN + const pipelineStack = new cdk.Stack(app, 'PipelineStack', { env: PIPELINE_ENV }); + const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk', { + selfMutation: false, + }); + pipeline.addStage(new StageWithStackOutput(pipelineStack, 'Stage')); + + // WHEN + pipeline.buildPipeline(); + + // THEN + expect(pipeline.selfMutationProject).toBeUndefined(); +}); + interface ReuseCodePipelineStackProps extends cdk.StackProps { reuseCrossRegionSupportStacks?: boolean; }