|
1 | 1 | <?xml version="1.0" encoding="utf-8"?> |
2 | 2 | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
3 | 3 |
|
4 | | - <!-- |
5 | | - CycloneDX.MSBuild Targets |
6 | | -
|
7 | | - This file defines MSBuild targets for automatic CycloneDX SBOM generation. |
8 | | - Targets are executed during the build process to generate SBOMs. |
9 | | -
|
10 | | - Security by Design: |
11 | | - - Tool runs in build context without elevated permissions |
12 | | - - Input validation on all properties |
13 | | - - Fail-safe defaults (continue on error) |
14 | | - - No arbitrary command execution |
15 | | -
|
16 | | - Clean Code Principles: |
17 | | - - Single Responsibility: Each target has one clear purpose |
18 | | - - Separation of Concerns: Tool installation, validation, and execution are separate |
19 | | - - DRY: Reusable property groups |
20 | | - --> |
| 4 | + <!-- CycloneDX.MSBuild Targets --> |
21 | 5 |
|
22 | 6 | <!-- Target execution order: |
23 | 7 | 1. ValidateCycloneDxConfiguration (before build) |
24 | 8 | 2. EnsureCycloneDxToolInstalled (before SBOM generation) |
25 | | - 3. GenerateCycloneDxSbom (after build, before pack) |
| 9 | + 3. GenerateCycloneDxMetadata (before SBOM generation if metadata requested) |
| 10 | + 4. GenerateCycloneDxSbom (after build, before pack) |
26 | 11 | --> |
27 | 12 |
|
28 | | - <!-- Inline task to URL-encode component name and version --> |
29 | | - <UsingTask TaskName="UrlEncodeCycloneDxProps" TaskFactory="CodeTaskFactory" AssemblyName="Microsoft.Build.Tasks.Core"> |
30 | | - <ParameterGroup> |
31 | | - <ComponentName ParameterType="System.String" Required="true" /> |
32 | | - <Version ParameterType="System.String" Required="true" /> |
33 | | - <EncodedComponentName ParameterType="System.String" Output="true" /> |
34 | | - <EncodedVersion ParameterType="System.String" Output="true" /> |
35 | | - </ParameterGroup> |
36 | | - <Task> |
37 | | - <Reference Include="System" /> |
38 | | - <Code Type="Fragment" Language="cs"> |
39 | | - <![CDATA[ |
40 | | - EncodedComponentName = System.Uri.EscapeDataString(ComponentName ?? ""); |
41 | | - EncodedVersion = System.Uri.EscapeDataString(Version ?? ""); |
42 | | - ]]> |
43 | | - </Code> |
44 | | - </Task> |
45 | | - </UsingTask> |
46 | | - |
47 | | - <!-- Inline task to XML-escape strings --> |
48 | | - <UsingTask TaskName="XmlEscapeCycloneDxProps" TaskFactory="CodeTaskFactory" AssemblyName="Microsoft.Build.Tasks.Core"> |
49 | | - <ParameterGroup> |
50 | | - <InputString ParameterType="System.String" Required="true" /> |
51 | | - <EscapedString ParameterType="System.String" Output="true" /> |
52 | | - </ParameterGroup> |
53 | | - <Task> |
54 | | - <Reference Include="System.Xml" /> |
55 | | - <Code Type="Fragment" Language="cs"> |
56 | | - <![CDATA[ |
57 | | - if (!string.IsNullOrEmpty(InputString)) |
58 | | - { |
59 | | - EscapedString = InputString |
60 | | - .Replace("&", "&") |
61 | | - .Replace("<", "<") |
62 | | - .Replace(">", ">") |
63 | | - .Replace("\"", """) |
64 | | - .Replace("'", "'"); |
65 | | - } |
66 | | - else |
67 | | - { |
68 | | - EscapedString = ""; |
69 | | - } |
70 | | - ]]> |
71 | | - </Code> |
72 | | - </Task> |
73 | | - </UsingTask> |
74 | | - |
75 | 13 | <Target Name="ValidateCycloneDxConfiguration" |
76 | 14 | BeforeTargets="Build" |
77 | 15 | Condition="'$(GenerateCycloneDxSbom)' == 'true'"> |
|
148 | 86 |
|
149 | 87 | <Target Name="GenerateCycloneDxMetadata" |
150 | 88 | BeforeTargets="GenerateCycloneDxSbom" |
151 | | - Condition="'$(GenerateCycloneDxSbom)' == 'true' AND '$(CycloneDxGenerateMetadata)' == 'true' AND '$(CycloneDxImportMetadataPath)' == ''"> |
| 89 | + Condition="'$(GenerateCycloneDxSbom)' == 'true' AND '$(CycloneDxGenerateMetadata)' == 'true' AND '$(CycloneDxImportMetadataPath)' == '' AND '$(MSBuildRuntimeType)' != 'Core'"> |
152 | 90 |
|
| 91 | + <!-- Collect raw values --> |
153 | 92 | <PropertyGroup> |
154 | | - <!-- Generate temporary metadata file path --> |
155 | 93 | <_CycloneDxTempMetadataPath>$(IntermediateOutputPath)cyclonedx-metadata.xml</_CycloneDxTempMetadataPath> |
156 | | - |
157 | | - <!-- Extract version from various possible sources --> |
158 | 94 | <_CycloneDxVersion Condition="'$(Version)' != ''">$(Version)</_CycloneDxVersion> |
159 | 95 | <_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == '' AND '$(VersionPrefix)' != ''">$(VersionPrefix)</_CycloneDxVersion> |
160 | 96 | <_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == '' AND '$(AssemblyVersion)' != ''">$(AssemblyVersion)</_CycloneDxVersion> |
161 | 97 | <_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == '' AND '$(GitVersion_FullSemVer)' != ''">$(GitVersion_FullSemVer)</_CycloneDxVersion> |
162 | 98 | <_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == '' AND '$(GitVersion_SemVer)' != ''">$(GitVersion_SemVer)</_CycloneDxVersion> |
163 | 99 | <_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == ''">1.0.0</_CycloneDxVersion> |
164 | | - |
165 | | - <!-- Extract component name --> |
166 | 100 | <_CycloneDxComponentName Condition="'$(AssemblyName)' != ''">$(AssemblyName)</_CycloneDxComponentName> |
167 | 101 | <_CycloneDxComponentName Condition="'$(_CycloneDxComponentName)' == ''">$(MSBuildProjectName)</_CycloneDxComponentName> |
168 | | - |
169 | | - <!-- Extract authors/company --> |
170 | 102 | <_CycloneDxAuthors Condition="'$(Authors)' != ''">$(Authors)</_CycloneDxAuthors> |
171 | 103 | <_CycloneDxAuthors Condition="'$(_CycloneDxAuthors)' == '' AND '$(Company)' != ''">$(Company)</_CycloneDxAuthors> |
172 | | - |
173 | | - <!-- Extract description --> |
174 | 104 | <_CycloneDxDescription Condition="'$(Description)' != ''">$(Description)</_CycloneDxDescription> |
175 | 105 | <_CycloneDxDescription Condition="'$(_CycloneDxDescription)' == '' AND '$(PackageDescription)' != ''">$(PackageDescription)</_CycloneDxDescription> |
176 | | - |
177 | | - <!-- Extract copyright --> |
178 | 106 | <_CycloneDxCopyright Condition="'$(Copyright)' != ''">$(Copyright)</_CycloneDxCopyright> |
179 | | - |
180 | | - <!-- Extract license --> |
181 | 107 | <_CycloneDxLicense Condition="'$(PackageLicenseExpression)' != ''">$(PackageLicenseExpression)</_CycloneDxLicense> |
182 | | - |
183 | | - <!-- Build package URL (purl) --> |
184 | 108 | </PropertyGroup> |
185 | 109 |
|
186 | | - <UrlEncodeCycloneDxProps |
187 | | - ComponentName="$(_CycloneDxComponentName)" |
188 | | - Version="$(_CycloneDxVersion)"> |
189 | | - <Output TaskParameter="EncodedComponentName" PropertyName="_CycloneDxComponentNameEncoded" /> |
190 | | - <Output TaskParameter="EncodedVersion" PropertyName="_CycloneDxVersionEncoded" /> |
191 | | - </UrlEncodeCycloneDxProps> |
192 | | - |
| 110 | + <!-- URL encode component name + version (basic passthrough, no inline tasks) --> |
193 | 111 | <PropertyGroup> |
| 112 | + <_CycloneDxComponentNameEncoded>$(_CycloneDxComponentName)</_CycloneDxComponentNameEncoded> |
| 113 | + <_CycloneDxVersionEncoded>$(_CycloneDxVersion)</_CycloneDxVersionEncoded> |
194 | 114 | <_CycloneDxPurl>pkg:nuget/$(_CycloneDxComponentNameEncoded)@$(_CycloneDxVersionEncoded)</_CycloneDxPurl> |
195 | 115 | </PropertyGroup> |
196 | 116 |
|
197 | | - <!-- Escape all values for XML --> |
198 | | - <XmlEscapeCycloneDxProps InputString="$(_CycloneDxPurl)"> |
199 | | - <Output TaskParameter="EscapedString" PropertyName="_CycloneDxPurlEscaped" /> |
200 | | - </XmlEscapeCycloneDxProps> |
201 | | - |
202 | | - <XmlEscapeCycloneDxProps InputString="$(_CycloneDxComponentName)"> |
203 | | - <Output TaskParameter="EscapedString" PropertyName="_CycloneDxComponentNameEscaped" /> |
204 | | - </XmlEscapeCycloneDxProps> |
205 | | - |
206 | | - <XmlEscapeCycloneDxProps InputString="$(_CycloneDxVersion)"> |
207 | | - <Output TaskParameter="EscapedString" PropertyName="_CycloneDxVersionEscaped" /> |
208 | | - </XmlEscapeCycloneDxProps> |
209 | | - |
210 | | - <XmlEscapeCycloneDxProps InputString="$(_CycloneDxDescription)" Condition="'$(_CycloneDxDescription)' != ''"> |
211 | | - <Output TaskParameter="EscapedString" PropertyName="_CycloneDxDescriptionEscaped" /> |
212 | | - </XmlEscapeCycloneDxProps> |
213 | | - |
214 | | - <XmlEscapeCycloneDxProps InputString="$(_CycloneDxAuthors)" Condition="'$(_CycloneDxAuthors)' != ''"> |
215 | | - <Output TaskParameter="EscapedString" PropertyName="_CycloneDxAuthorsEscaped" /> |
216 | | - </XmlEscapeCycloneDxProps> |
217 | | - |
218 | | - <XmlEscapeCycloneDxProps InputString="$(_CycloneDxCopyright)" Condition="'$(_CycloneDxCopyright)' != ''"> |
219 | | - <Output TaskParameter="EscapedString" PropertyName="_CycloneDxCopyrightEscaped" /> |
220 | | - </XmlEscapeCycloneDxProps> |
221 | | - |
222 | | - <XmlEscapeCycloneDxProps InputString="$(_CycloneDxLicense)" Condition="'$(_CycloneDxLicense)' != ''"> |
223 | | - <Output TaskParameter="EscapedString" PropertyName="_CycloneDxLicenseEscaped" /> |
224 | | - </XmlEscapeCycloneDxProps> |
225 | | - |
226 | | - <Message Importance="low" Text="[CycloneDX] Generating metadata from assembly information..." /> |
227 | | - <Message Importance="low" Text="[CycloneDX] Version: $(_CycloneDxVersion)" /> |
228 | | - <Message Importance="low" Text="[CycloneDX] Name: $(_CycloneDxComponentName)" /> |
229 | | - <Message Importance="low" Text="[CycloneDX] Authors: $(_CycloneDxAuthors)" Condition="'$(_CycloneDxAuthors)' != ''" /> |
230 | | - |
231 | | - <!-- Build metadata XML content using escaped values without CDATA interleaving --> |
| 117 | + <!-- Build metadata XML content (no special char escaping) --> |
232 | 118 | <PropertyGroup> |
233 | | - <_CycloneDxMetadataContent> |
234 | | - <?xml version="1.0" encoding="utf-8"?> |
235 | | - <bom xmlns="http://cyclonedx.org/schema/bom/1.6"> |
236 | | - <metadata> |
237 | | - <component type="application" bom-ref="$(_CycloneDxPurlEscaped)"> |
238 | | - <name>$(_CycloneDxComponentNameEscaped)</name> |
239 | | - <version>$(_CycloneDxVersionEscaped)</version> |
240 | | - </_CycloneDxMetadataContent> |
241 | | - |
242 | | - <!-- Add description if available --> |
243 | | - <_CycloneDxMetadataContent Condition="'$(_CycloneDxDescription)' != ''">$(_CycloneDxMetadataContent) <description>$(_CycloneDxDescriptionEscaped)</description></_CycloneDxMetadataContent> |
244 | | - |
245 | | - <!-- Add authors/supplier if available --> |
246 | | - <_CycloneDxMetadataContent Condition="'$(_CycloneDxAuthors)' != ''">$(_CycloneDxMetadataContent) |
247 | | - <supplier> |
248 | | - <name>$(_CycloneDxAuthorsEscaped)</name> |
249 | | - </supplier></_CycloneDxMetadataContent> |
250 | | - |
251 | | - <!-- Add copyright if available --> |
252 | | - <_CycloneDxMetadataContent Condition="'$(_CycloneDxCopyright)' != ''">$(_CycloneDxMetadataContent) <copyright>$(_CycloneDxCopyrightEscaped)</copyright></_CycloneDxMetadataContent> |
253 | | - |
254 | | - <!-- Add license if available --> |
255 | | - <_CycloneDxMetadataContent Condition="'$(_CycloneDxLicense)' != ''">$(_CycloneDxMetadataContent) |
256 | | - <licenses> |
257 | | - <license> |
258 | | - <id>$(_CycloneDxLicenseEscaped)</id> |
259 | | - </license> |
260 | | - </licenses></_CycloneDxMetadataContent> |
261 | | - |
262 | | - <!-- Add purl and close tags --> |
263 | | - <_CycloneDxMetadataContent>$(_CycloneDxMetadataContent) |
264 | | - <purl>$(_CycloneDxPurlEscaped)</purl> |
265 | | - </component> |
266 | | - </metadata> |
267 | | - </bom></_CycloneDxMetadataContent> |
| 119 | + <_CycloneDxMetadataContent><?xml version="1.0" encoding="utf-8"?><bom xmlns="http://cyclonedx.org/schema/bom/1.6"><metadata><component type="application" bom-ref="$(_CycloneDxPurl)"><name>$(_CycloneDxComponentName)</name><version>$(_CycloneDxVersion)</version></_CycloneDxMetadataContent> |
| 120 | + <_CycloneDxMetadataContent Condition="'$(_CycloneDxDescription)' != ''">$(_CycloneDxMetadataContent)<description>$(_CycloneDxDescription)</description></_CycloneDxMetadataContent> |
| 121 | + <_CycloneDxMetadataContent Condition="'$(_CycloneDxAuthors)' != ''">$(_CycloneDxMetadataContent)<supplier><name>$(_CycloneDxAuthors)</name></supplier></_CycloneDxMetadataContent> |
| 122 | + <_CycloneDxMetadataContent Condition="'$(_CycloneDxCopyright)' != ''">$(_CycloneDxMetadataContent)<copyright>$(_CycloneDxCopyright)</copyright></_CycloneDxMetadataContent> |
| 123 | + <_CycloneDxMetadataContent Condition="'$(_CycloneDxLicense)' != ''">$(_CycloneDxMetadataContent)<licenses><license><id>$(_CycloneDxLicense)</id></license></licenses></_CycloneDxMetadataContent> |
| 124 | + <_CycloneDxMetadataContent>$(_CycloneDxMetadataContent)<purl>$(_CycloneDxPurl)</purl></component></metadata></bom></_CycloneDxMetadataContent> |
268 | 125 | </PropertyGroup> |
269 | 126 |
|
270 | | - <!-- Write metadata to temporary file --> |
271 | | - <WriteLinesToFile |
272 | | - File="$(_CycloneDxTempMetadataPath)" |
273 | | - Lines="$(_CycloneDxMetadataContent)" |
274 | | - Overwrite="true" |
275 | | - WriteOnlyWhenDifferent="true" /> |
| 127 | + <WriteLinesToFile File="$(_CycloneDxTempMetadataPath)" |
| 128 | + Lines="$(_CycloneDxMetadataContent)" |
| 129 | + Overwrite="true" |
| 130 | + WriteOnlyWhenDifferent="true" /> |
276 | 131 |
|
277 | | - <!-- Set the import path to the generated metadata file --> |
278 | 132 | <PropertyGroup> |
279 | 133 | <CycloneDxImportMetadataPath>$(_CycloneDxTempMetadataPath)</CycloneDxImportMetadataPath> |
280 | 134 | </PropertyGroup> |
281 | 135 |
|
282 | 136 | <Message Importance="low" Text="[CycloneDX] Metadata template generated: $(_CycloneDxTempMetadataPath)" /> |
283 | | - |
284 | 137 | </Target> |
285 | 138 |
|
286 | 139 | <Target Name="GenerateCycloneDxSbom" |
|
289 | 142 |
|
290 | 143 | <Message Importance="high" Text="[CycloneDX] Generating SBOM for $(MSBuildProjectName)..." /> |
291 | 144 |
|
292 | | - <!-- Set output directory default now, when $(OutputPath) is available --> |
293 | 145 | <PropertyGroup> |
294 | 146 | <CycloneDxOutputDirectory Condition="'$(CycloneDxOutputDirectory)' == ''">$(OutputPath)</CycloneDxOutputDirectory> |
295 | | - <!-- Remove trailing backslash/slash to avoid escaping the closing quote in command-line arguments --> |
296 | | - <CycloneDxOutputDirectory>$(CycloneDxOutputDirectory.TrimEnd('\\').TrimEnd('/'))</CycloneDxOutputDirectory> |
| 147 | + <CycloneDxOutputDirectory>$(CycloneDxOutputDirectory.TrimEnd('\').TrimEnd('/'))</CycloneDxOutputDirectory> |
297 | 148 | </PropertyGroup> |
298 | 149 |
|
299 | 150 | <PropertyGroup> |
300 | | - <!-- Build command line arguments --> |
301 | 151 | <_CycloneDxArgs></_CycloneDxArgs> |
302 | 152 | <_CycloneDxArgs>$(_CycloneDxArgs) "$(MSBuildProjectFullPath)"</_CycloneDxArgs> |
303 | 153 | <_CycloneDxArgs>$(_CycloneDxArgs) -o "$(CycloneDxOutputDirectory)"</_CycloneDxArgs> |
304 | | - |
305 | | - <!-- Build full filename with extension based on format --> |
306 | 154 | <_CycloneDxFullFilename Condition="'$(CycloneDxOutputFormat)' == 'json'">$(CycloneDxOutputFilename).json</_CycloneDxFullFilename> |
307 | 155 | <_CycloneDxFullFilename Condition="'$(CycloneDxOutputFormat)' == 'xml'">$(CycloneDxOutputFilename).xml</_CycloneDxFullFilename> |
308 | | - <!-- Fallback to json if format is invalid --> |
309 | 156 | <_CycloneDxFullFilename Condition="'$(_CycloneDxFullFilename)' == ''">$(CycloneDxOutputFilename).json</_CycloneDxFullFilename> |
310 | 157 | <_CycloneDxArgs>$(_CycloneDxArgs) -fn "$(_CycloneDxFullFilename)"</_CycloneDxArgs> |
311 | | - |
312 | | - <!-- Output format: json or xml (capitalize first letter for tool) --> |
313 | 158 | <_CycloneDxOutputFormat Condition="'$(CycloneDxOutputFormat)' == 'json'">Json</_CycloneDxOutputFormat> |
314 | 159 | <_CycloneDxOutputFormat Condition="'$(CycloneDxOutputFormat)' == 'xml'">Xml</_CycloneDxOutputFormat> |
315 | | - <!-- Fallback to Json if format is invalid --> |
316 | 160 | <_CycloneDxOutputFormat Condition="'$(_CycloneDxOutputFormat)' == ''">Json</_CycloneDxOutputFormat> |
317 | 161 | <_CycloneDxArgs>$(_CycloneDxArgs) -F $(_CycloneDxOutputFormat)</_CycloneDxArgs> |
318 | | - |
319 | | - <!-- Optional arguments --> |
320 | 162 | <_CycloneDxArgs Condition="'$(CycloneDxExcludeDev)' == 'true'">$(_CycloneDxArgs) -ed</_CycloneDxArgs> |
321 | 163 | <_CycloneDxArgs Condition="'$(CycloneDxExcludeTestProjects)' == 'true'">$(_CycloneDxArgs) -t</_CycloneDxArgs> |
322 | 164 | <_CycloneDxArgs Condition="'$(CycloneDxDisableSerialNumber)' == 'true'">$(_CycloneDxArgs) -ns</_CycloneDxArgs> |
|
326 | 168 | <_CycloneDxArgs Condition="'$(CycloneDxImportMetadataPath)' != ''">$(_CycloneDxArgs) -imp "$(CycloneDxImportMetadataPath)"</_CycloneDxArgs> |
327 | 169 | </PropertyGroup> |
328 | 170 |
|
329 | | - <!-- Execute CycloneDX tool --> |
330 | | - <Exec |
331 | | - Command="dotnet cyclonedx $(_CycloneDxArgs)" |
332 | | - WorkingDirectory="$(MSBuildProjectDirectory)" |
333 | | - ContinueOnError="$(CycloneDxContinueOnError)" |
334 | | - StandardOutputImportance="normal" |
335 | | - StandardErrorImportance="high"> |
| 171 | + <Exec Command="dotnet cyclonedx $(_CycloneDxArgs)" |
| 172 | + WorkingDirectory="$(MSBuildProjectDirectory)" |
| 173 | + ContinueOnError="$(CycloneDxContinueOnError)" |
| 174 | + StandardOutputImportance="normal" |
| 175 | + StandardErrorImportance="high"> |
336 | 176 | <Output TaskParameter="ExitCode" PropertyName="_CycloneDxGenerateExitCode" /> |
337 | 177 | </Exec> |
338 | 178 |
|
339 | | - <!-- Report success or failure --> |
340 | | - <Message |
341 | | - Importance="high" |
342 | | - Condition="'$(_CycloneDxGenerateExitCode)' == '0'" |
343 | | - Text="[CycloneDX] SBOM generated successfully: $(CycloneDxOutputPath)" /> |
| 179 | + <Message Importance="high" Condition="'$(_CycloneDxGenerateExitCode)' == '0'" Text="[CycloneDX] SBOM generated successfully: $(CycloneDxOutputPath)" /> |
344 | 180 |
|
345 | 181 | <Warning |
346 | 182 | Condition="'$(_CycloneDxGenerateExitCode)' != '0' AND '$(CycloneDxContinueOnError)' == 'true'" |
|
352 | 188 |
|
353 | 189 | </Target> |
354 | 190 |
|
355 | | - <!-- Optional: Include SBOM in NuGet package --> |
356 | 191 | <Target Name="IncludeCycloneDxSbomInPackage" |
357 | 192 | BeforeTargets="GenerateNuspec" |
358 | 193 | Condition="'$(GenerateCycloneDxSbom)' == 'true' AND '$(IsPackable)' != 'false'"> |
|
368 | 203 |
|
369 | 204 | </Target> |
370 | 205 |
|
371 | | - <!-- Copy SBOM to publish directory after publish --> |
372 | 206 | <Target Name="CopyCycloneDxSbomToPublishDirectory" |
373 | 207 | AfterTargets="Publish" |
374 | 208 | Condition="'$(GenerateCycloneDxSbom)' == 'true' and '$(PublishDir)' != ''"> |
375 | 209 |
|
376 | 210 | <PropertyGroup> |
377 | | - <!-- Determine the target path in publish directory --> |
378 | 211 | <_CycloneDxPublishPath>$(PublishDir)$(_CycloneDxFullFilename)</_CycloneDxPublishPath> |
379 | 212 | </PropertyGroup> |
380 | 213 |
|
381 | 214 | <Message Importance="normal" Text="[CycloneDX] Copying SBOM to publish directory..." /> |
382 | 215 |
|
383 | | - <!-- Copy the SBOM file to publish directory --> |
384 | | - <Copy |
385 | | - SourceFiles="$(CycloneDxOutputPath)" |
386 | | - DestinationFiles="$(_CycloneDxPublishPath)" |
387 | | - SkipUnchangedFiles="true" |
388 | | - Condition="Exists('$(CycloneDxOutputPath)')"> |
389 | | - </Copy> |
| 216 | + <Copy SourceFiles="$(CycloneDxOutputPath)" |
| 217 | + DestinationFiles="$(_CycloneDxPublishPath)" |
| 218 | + SkipUnchangedFiles="true" |
| 219 | + Condition="Exists('$(CycloneDxOutputPath)')" /> |
390 | 220 |
|
391 | 221 | <Message |
392 | 222 | Importance="high" |
|
0 commit comments