Skip to content

Commit 7b8ebc8

Browse files
committed
refactor(msbuild): simplify CycloneDX target handling
- Entferne Inline-Tasks `UrlEncodeCycloneDxProps` und `XmlEscapeCycloneDxProps`, um Escape-Operationen zu vermeiden. - Füge neues Ziel `GenerateCycloneDxMetadata` vor der SBOM-Generierung hinzu. - Aktualisiere Bedingung für `GenerateCycloneDxMetadata`, um .NET Core auszuschließen. - Optimiere Metadaten-Generierung durch direkte XML-Inhaltsverarbeitung. - Vereinfachung der `WriteLinesToFile`- und `Exec`-Tasks. - Entferne überflüssige Kommentare und Bedingungsprüfungen für bessere Lesbarkeit. - Behalte Ziele `IncludeCycloneDxSbomInPackage` und `CopyCycloneDxSbomToPublishDirectory` bei, jedoch mit vereinfachter Struktur.
1 parent 5ca72e3 commit 7b8ebc8

File tree

1 file changed

+30
-200
lines changed

1 file changed

+30
-200
lines changed

src/CycloneDX.MSBuild/build/CycloneDX.MSBuild.targets

Lines changed: 30 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,15 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33

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 -->
215

226
<!-- Target execution order:
237
1. ValidateCycloneDxConfiguration (before build)
248
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)
2611
-->
2712

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("&", "&amp;")
61-
.Replace("<", "&lt;")
62-
.Replace(">", "&gt;")
63-
.Replace("\"", "&quot;")
64-
.Replace("'", "&apos;");
65-
}
66-
else
67-
{
68-
EscapedString = "";
69-
}
70-
]]>
71-
</Code>
72-
</Task>
73-
</UsingTask>
74-
7513
<Target Name="ValidateCycloneDxConfiguration"
7614
BeforeTargets="Build"
7715
Condition="'$(GenerateCycloneDxSbom)' == 'true'">
@@ -148,139 +86,54 @@
14886

14987
<Target Name="GenerateCycloneDxMetadata"
15088
BeforeTargets="GenerateCycloneDxSbom"
151-
Condition="'$(GenerateCycloneDxSbom)' == 'true' AND '$(CycloneDxGenerateMetadata)' == 'true' AND '$(CycloneDxImportMetadataPath)' == ''">
89+
Condition="'$(GenerateCycloneDxSbom)' == 'true' AND '$(CycloneDxGenerateMetadata)' == 'true' AND '$(CycloneDxImportMetadataPath)' == '' AND '$(MSBuildRuntimeType)' != 'Core'">
15290

91+
<!-- Collect raw values -->
15392
<PropertyGroup>
154-
<!-- Generate temporary metadata file path -->
15593
<_CycloneDxTempMetadataPath>$(IntermediateOutputPath)cyclonedx-metadata.xml</_CycloneDxTempMetadataPath>
156-
157-
<!-- Extract version from various possible sources -->
15894
<_CycloneDxVersion Condition="'$(Version)' != ''">$(Version)</_CycloneDxVersion>
15995
<_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == '' AND '$(VersionPrefix)' != ''">$(VersionPrefix)</_CycloneDxVersion>
16096
<_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == '' AND '$(AssemblyVersion)' != ''">$(AssemblyVersion)</_CycloneDxVersion>
16197
<_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == '' AND '$(GitVersion_FullSemVer)' != ''">$(GitVersion_FullSemVer)</_CycloneDxVersion>
16298
<_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == '' AND '$(GitVersion_SemVer)' != ''">$(GitVersion_SemVer)</_CycloneDxVersion>
16399
<_CycloneDxVersion Condition="'$(_CycloneDxVersion)' == ''">1.0.0</_CycloneDxVersion>
164-
165-
<!-- Extract component name -->
166100
<_CycloneDxComponentName Condition="'$(AssemblyName)' != ''">$(AssemblyName)</_CycloneDxComponentName>
167101
<_CycloneDxComponentName Condition="'$(_CycloneDxComponentName)' == ''">$(MSBuildProjectName)</_CycloneDxComponentName>
168-
169-
<!-- Extract authors/company -->
170102
<_CycloneDxAuthors Condition="'$(Authors)' != ''">$(Authors)</_CycloneDxAuthors>
171103
<_CycloneDxAuthors Condition="'$(_CycloneDxAuthors)' == '' AND '$(Company)' != ''">$(Company)</_CycloneDxAuthors>
172-
173-
<!-- Extract description -->
174104
<_CycloneDxDescription Condition="'$(Description)' != ''">$(Description)</_CycloneDxDescription>
175105
<_CycloneDxDescription Condition="'$(_CycloneDxDescription)' == '' AND '$(PackageDescription)' != ''">$(PackageDescription)</_CycloneDxDescription>
176-
177-
<!-- Extract copyright -->
178106
<_CycloneDxCopyright Condition="'$(Copyright)' != ''">$(Copyright)</_CycloneDxCopyright>
179-
180-
<!-- Extract license -->
181107
<_CycloneDxLicense Condition="'$(PackageLicenseExpression)' != ''">$(PackageLicenseExpression)</_CycloneDxLicense>
182-
183-
<!-- Build package URL (purl) -->
184108
</PropertyGroup>
185109

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) -->
193111
<PropertyGroup>
112+
<_CycloneDxComponentNameEncoded>$(_CycloneDxComponentName)</_CycloneDxComponentNameEncoded>
113+
<_CycloneDxVersionEncoded>$(_CycloneDxVersion)</_CycloneDxVersionEncoded>
194114
<_CycloneDxPurl>pkg:nuget/$(_CycloneDxComponentNameEncoded)@$(_CycloneDxVersionEncoded)</_CycloneDxPurl>
195115
</PropertyGroup>
196116

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) -->
232118
<PropertyGroup>
233-
<_CycloneDxMetadataContent>
234-
&lt;?xml version="1.0" encoding="utf-8"?&gt;
235-
&lt;bom xmlns="http://cyclonedx.org/schema/bom/1.6"&gt;
236-
&lt;metadata&gt;
237-
&lt;component type="application" bom-ref="$(_CycloneDxPurlEscaped)"&gt;
238-
&lt;name&gt;$(_CycloneDxComponentNameEscaped)&lt;/name&gt;
239-
&lt;version&gt;$(_CycloneDxVersionEscaped)&lt;/version&gt;
240-
</_CycloneDxMetadataContent>
241-
242-
<!-- Add description if available -->
243-
<_CycloneDxMetadataContent Condition="'$(_CycloneDxDescription)' != ''">$(_CycloneDxMetadataContent) &lt;description&gt;$(_CycloneDxDescriptionEscaped)&lt;/description&gt;</_CycloneDxMetadataContent>
244-
245-
<!-- Add authors/supplier if available -->
246-
<_CycloneDxMetadataContent Condition="'$(_CycloneDxAuthors)' != ''">$(_CycloneDxMetadataContent)
247-
&lt;supplier&gt;
248-
&lt;name&gt;$(_CycloneDxAuthorsEscaped)&lt;/name&gt;
249-
&lt;/supplier&gt;</_CycloneDxMetadataContent>
250-
251-
<!-- Add copyright if available -->
252-
<_CycloneDxMetadataContent Condition="'$(_CycloneDxCopyright)' != ''">$(_CycloneDxMetadataContent) &lt;copyright&gt;$(_CycloneDxCopyrightEscaped)&lt;/copyright&gt;</_CycloneDxMetadataContent>
253-
254-
<!-- Add license if available -->
255-
<_CycloneDxMetadataContent Condition="'$(_CycloneDxLicense)' != ''">$(_CycloneDxMetadataContent)
256-
&lt;licenses&gt;
257-
&lt;license&gt;
258-
&lt;id&gt;$(_CycloneDxLicenseEscaped)&lt;/id&gt;
259-
&lt;/license&gt;
260-
&lt;/licenses&gt;</_CycloneDxMetadataContent>
261-
262-
<!-- Add purl and close tags -->
263-
<_CycloneDxMetadataContent>$(_CycloneDxMetadataContent)
264-
&lt;purl&gt;$(_CycloneDxPurlEscaped)&lt;/purl&gt;
265-
&lt;/component&gt;
266-
&lt;/metadata&gt;
267-
&lt;/bom&gt;</_CycloneDxMetadataContent>
119+
<_CycloneDxMetadataContent>&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;bom xmlns="http://cyclonedx.org/schema/bom/1.6"&gt;&lt;metadata&gt;&lt;component type="application" bom-ref="$(_CycloneDxPurl)"&gt;&lt;name&gt;$(_CycloneDxComponentName)&lt;/name&gt;&lt;version&gt;$(_CycloneDxVersion)&lt;/version&gt;</_CycloneDxMetadataContent>
120+
<_CycloneDxMetadataContent Condition="'$(_CycloneDxDescription)' != ''">$(_CycloneDxMetadataContent)&lt;description&gt;$(_CycloneDxDescription)&lt;/description&gt;</_CycloneDxMetadataContent>
121+
<_CycloneDxMetadataContent Condition="'$(_CycloneDxAuthors)' != ''">$(_CycloneDxMetadataContent)&lt;supplier&gt;&lt;name&gt;$(_CycloneDxAuthors)&lt;/name&gt;&lt;/supplier&gt;</_CycloneDxMetadataContent>
122+
<_CycloneDxMetadataContent Condition="'$(_CycloneDxCopyright)' != ''">$(_CycloneDxMetadataContent)&lt;copyright&gt;$(_CycloneDxCopyright)&lt;/copyright&gt;</_CycloneDxMetadataContent>
123+
<_CycloneDxMetadataContent Condition="'$(_CycloneDxLicense)' != ''">$(_CycloneDxMetadataContent)&lt;licenses&gt;&lt;license&gt;&lt;id&gt;$(_CycloneDxLicense)&lt;/id&gt;&lt;/license&gt;&lt;/licenses&gt;</_CycloneDxMetadataContent>
124+
<_CycloneDxMetadataContent>$(_CycloneDxMetadataContent)&lt;purl&gt;$(_CycloneDxPurl)&lt;/purl&gt;&lt;/component&gt;&lt;/metadata&gt;&lt;/bom&gt;</_CycloneDxMetadataContent>
268125
</PropertyGroup>
269126

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" />
276131

277-
<!-- Set the import path to the generated metadata file -->
278132
<PropertyGroup>
279133
<CycloneDxImportMetadataPath>$(_CycloneDxTempMetadataPath)</CycloneDxImportMetadataPath>
280134
</PropertyGroup>
281135

282136
<Message Importance="low" Text="[CycloneDX] Metadata template generated: $(_CycloneDxTempMetadataPath)" />
283-
284137
</Target>
285138

286139
<Target Name="GenerateCycloneDxSbom"
@@ -289,34 +142,23 @@
289142

290143
<Message Importance="high" Text="[CycloneDX] Generating SBOM for $(MSBuildProjectName)..." />
291144

292-
<!-- Set output directory default now, when $(OutputPath) is available -->
293145
<PropertyGroup>
294146
<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>
297148
</PropertyGroup>
298149

299150
<PropertyGroup>
300-
<!-- Build command line arguments -->
301151
<_CycloneDxArgs></_CycloneDxArgs>
302152
<_CycloneDxArgs>$(_CycloneDxArgs) "$(MSBuildProjectFullPath)"</_CycloneDxArgs>
303153
<_CycloneDxArgs>$(_CycloneDxArgs) -o "$(CycloneDxOutputDirectory)"</_CycloneDxArgs>
304-
305-
<!-- Build full filename with extension based on format -->
306154
<_CycloneDxFullFilename Condition="'$(CycloneDxOutputFormat)' == 'json'">$(CycloneDxOutputFilename).json</_CycloneDxFullFilename>
307155
<_CycloneDxFullFilename Condition="'$(CycloneDxOutputFormat)' == 'xml'">$(CycloneDxOutputFilename).xml</_CycloneDxFullFilename>
308-
<!-- Fallback to json if format is invalid -->
309156
<_CycloneDxFullFilename Condition="'$(_CycloneDxFullFilename)' == ''">$(CycloneDxOutputFilename).json</_CycloneDxFullFilename>
310157
<_CycloneDxArgs>$(_CycloneDxArgs) -fn "$(_CycloneDxFullFilename)"</_CycloneDxArgs>
311-
312-
<!-- Output format: json or xml (capitalize first letter for tool) -->
313158
<_CycloneDxOutputFormat Condition="'$(CycloneDxOutputFormat)' == 'json'">Json</_CycloneDxOutputFormat>
314159
<_CycloneDxOutputFormat Condition="'$(CycloneDxOutputFormat)' == 'xml'">Xml</_CycloneDxOutputFormat>
315-
<!-- Fallback to Json if format is invalid -->
316160
<_CycloneDxOutputFormat Condition="'$(_CycloneDxOutputFormat)' == ''">Json</_CycloneDxOutputFormat>
317161
<_CycloneDxArgs>$(_CycloneDxArgs) -F $(_CycloneDxOutputFormat)</_CycloneDxArgs>
318-
319-
<!-- Optional arguments -->
320162
<_CycloneDxArgs Condition="'$(CycloneDxExcludeDev)' == 'true'">$(_CycloneDxArgs) -ed</_CycloneDxArgs>
321163
<_CycloneDxArgs Condition="'$(CycloneDxExcludeTestProjects)' == 'true'">$(_CycloneDxArgs) -t</_CycloneDxArgs>
322164
<_CycloneDxArgs Condition="'$(CycloneDxDisableSerialNumber)' == 'true'">$(_CycloneDxArgs) -ns</_CycloneDxArgs>
@@ -326,21 +168,15 @@
326168
<_CycloneDxArgs Condition="'$(CycloneDxImportMetadataPath)' != ''">$(_CycloneDxArgs) -imp "$(CycloneDxImportMetadataPath)"</_CycloneDxArgs>
327169
</PropertyGroup>
328170

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">
336176
<Output TaskParameter="ExitCode" PropertyName="_CycloneDxGenerateExitCode" />
337177
</Exec>
338178

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)" />
344180

345181
<Warning
346182
Condition="'$(_CycloneDxGenerateExitCode)' != '0' AND '$(CycloneDxContinueOnError)' == 'true'"
@@ -352,7 +188,6 @@
352188

353189
</Target>
354190

355-
<!-- Optional: Include SBOM in NuGet package -->
356191
<Target Name="IncludeCycloneDxSbomInPackage"
357192
BeforeTargets="GenerateNuspec"
358193
Condition="'$(GenerateCycloneDxSbom)' == 'true' AND '$(IsPackable)' != 'false'">
@@ -368,25 +203,20 @@
368203

369204
</Target>
370205

371-
<!-- Copy SBOM to publish directory after publish -->
372206
<Target Name="CopyCycloneDxSbomToPublishDirectory"
373207
AfterTargets="Publish"
374208
Condition="'$(GenerateCycloneDxSbom)' == 'true' and '$(PublishDir)' != ''">
375209

376210
<PropertyGroup>
377-
<!-- Determine the target path in publish directory -->
378211
<_CycloneDxPublishPath>$(PublishDir)$(_CycloneDxFullFilename)</_CycloneDxPublishPath>
379212
</PropertyGroup>
380213

381214
<Message Importance="normal" Text="[CycloneDX] Copying SBOM to publish directory..." />
382215

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)')" />
390220

391221
<Message
392222
Importance="high"

0 commit comments

Comments
 (0)