Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,18 @@ sections:
tests:
- net10.0@Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument
- net10.0@Spdx2JsonSerializer_SerializeDocument_CorrectResults

- id: REQ-PLAT-004
title: The library shall support the .NET Standard 2.0 target framework.
tags:
- platform
justification: |
.NET Standard 2.0 is a widely-supported target framework that enables the library to
be used in MSBuild extensions and other tooling that requires .NET Standard compatibility.
Supporting this target framework ensures the library can be integrated into a broader
range of .NET projects, including those targeting .NET Framework and older .NET Core versions.
The net481 test target on Windows provides direct runtime evidence of .NET Standard 2.0
compatibility, as .NET Framework 4.8.1 fully implements the .NET Standard 2.0 API surface.
tests:
- "net481@Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument"
- "net481@Spdx2JsonSerializer_SerializeDocument_CorrectResults"
33 changes: 28 additions & 5 deletions src/DemaConsulting.SpdxModel/DemaConsulting.SpdxModel.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<TargetFrameworks>netstandard2.0;net8.0;net9.0;net10.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand Down Expand Up @@ -31,32 +31,55 @@

<!-- Code Quality Configuration -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>

<!-- Polyfill Configuration -->
<PolyArgumentExceptions>true</PolyArgumentExceptions>

<!-- SBOM Configuration -->
<GenerateSBOM>true</GenerateSBOM>
<SBOMPackageName>$(PackageId)</SBOMPackageName>
<SBOMPackageVersion>$(Version)</SBOMPackageVersion>
<SBOMPackageSupplier>Organization: $(Company)</SBOMPackageSupplier>
</PropertyGroup>

<!-- Build Tool Dependencies -->
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="10.0.3" />
<PackageReference Include="Microsoft.Sbom.Targets" Version="4.1.5" PrivateAssets="All" />
<PackageReference Include="Polyfill" Version="9.12.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103" PrivateAssets="All" />
</ItemGroup>

<!-- Code Analysis Dependencies -->
<ItemGroup>
<!-- Analyzer packages use child-element form to configure both PrivateAssets and IncludeAssets:
- PrivateAssets="all" prevents these build-time analyzers from becoming transitive dependencies
in packages that consume this library.
- IncludeAssets lists all asset types (including 'analyzers' and 'buildtransitive') to ensure
Roslyn analyzers and MSBuild targets are fully activated during the build. -->
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.103">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.19.0.132793">
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.20.0.135146">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<!-- Library Dependencies -->
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="9.0.5" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
<PackageReference Include="System.Text.Json" Version="10.0.3" Condition="'$(TargetFramework)' != 'netstandard2.0'" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="DemaConsulting.SpdxModel.Tests" />
</ItemGroup>

<ItemGroup>
<None Remove="DemaConsulting.SpdxModel.csproj.DotSettings" />
</ItemGroup>
Expand Down
100 changes: 100 additions & 0 deletions src/DemaConsulting.SpdxModel/HashCodePolyfill.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright(c) 2024 DEMA Consulting
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#if NETSTANDARD2_0

// Provide a minimal HashCode.Combine polyfill for netstandard2.0, which does not
// have System.HashCode. The implementation uses prime-based accumulation to
// produce reasonably well-distributed hash codes from multiple values, matching
// the public API surface used in this library.
namespace System;

/// <summary>
/// Polyfill for <c>System.HashCode</c> on netstandard2.0 targets.
/// </summary>
/// <remarks>
/// System.HashCode was introduced in .NET Core 2.1 / netstandard2.1 and is not
/// available in netstandard2.0. This internal struct provides the Combine overloads
/// used by this library so that no conditional compilation is needed in calling code.
/// </remarks>
internal struct HashCode
{
/// <summary>
/// Combines two values into a hash code using prime-based accumulation.
/// </summary>
/// <typeparam name="T1">Type of the first value</typeparam>
/// <typeparam name="T2">Type of the second value</typeparam>
/// <param name="value1">First value</param>
/// <param name="value2">Second value</param>
/// <returns>Combined hash code</returns>
public static int Combine<T1, T2>(T1 value1, T2 value2)
{
// Accumulate hash codes using a prime multiplier to reduce collisions
var hash = 17;
hash = hash * 31 + (value1?.GetHashCode() ?? 0);
hash = hash * 31 + (value2?.GetHashCode() ?? 0);
return hash;
}

/// <summary>
/// Combines three values into a hash code using prime-based accumulation.
/// </summary>
/// <typeparam name="T1">Type of the first value</typeparam>
/// <typeparam name="T2">Type of the second value</typeparam>
/// <typeparam name="T3">Type of the third value</typeparam>
/// <param name="value1">First value</param>
/// <param name="value2">Second value</param>
/// <param name="value3">Third value</param>
/// <returns>Combined hash code</returns>
public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3)
{
// Accumulate hash codes using a prime multiplier to reduce collisions
var hash = 17;
hash = hash * 31 + (value1?.GetHashCode() ?? 0);
hash = hash * 31 + (value2?.GetHashCode() ?? 0);
hash = hash * 31 + (value3?.GetHashCode() ?? 0);
return hash;
}

/// <summary>
/// Combines four values into a hash code using prime-based accumulation.
/// </summary>
/// <typeparam name="T1">Type of the first value</typeparam>
/// <typeparam name="T2">Type of the second value</typeparam>
/// <typeparam name="T3">Type of the third value</typeparam>
/// <typeparam name="T4">Type of the fourth value</typeparam>
/// <param name="value1">First value</param>
/// <param name="value2">Second value</param>
/// <param name="value3">Third value</param>
/// <param name="value4">Fourth value</param>
/// <returns>Combined hash code</returns>
public static int Combine<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4)
{
// Accumulate hash codes using a prime multiplier to reduce collisions
var hash = 17;
hash = hash * 31 + (value1?.GetHashCode() ?? 0);
hash = hash * 31 + (value2?.GetHashCode() ?? 0);
hash = hash * 31 + (value3?.GetHashCode() ?? 0);
hash = hash * 31 + (value4?.GetHashCode() ?? 0);
return hash;
}
}

#endif
16 changes: 15 additions & 1 deletion src/DemaConsulting.SpdxModel/SpdxHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,25 @@ namespace DemaConsulting.SpdxModel;

internal static partial class SpdxHelpers
{
#if NET7_0_OR_GREATER
/// <summary>
/// Regular expression for checking date/time formats
/// Regular expression for checking date/time formats (source-generated for .NET 7+)
/// </summary>
[GeneratedRegex(@"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$", RegexOptions.None, 100)]
private static partial Regex DateTimeRegex();
#else
/// <summary>
/// Cached regular expression instance for checking date/time formats (netstandard2.0 fallback)
/// </summary>
private static readonly Regex DateTimeRegexInstance =
new Regex(@"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$", RegexOptions.None, TimeSpan.FromMilliseconds(100));

/// <summary>
/// Regular expression for checking date/time formats
/// </summary>
/// <returns>Compiled <see cref="Regex"/> instance</returns>
private static Regex DateTimeRegex() => DateTimeRegexInstance;
#endif

/// <summary>
/// Test if a string is a valid SPDX date/time field (which include null/empty)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">net481;net8.0;net9.0;net10.0</TargetFrameworks>
<TargetFrameworks Condition="!$([MSBuild]::IsOSPlatform('Windows'))">net8.0;net9.0;net10.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand All @@ -12,47 +13,62 @@

<!-- Code Quality Configuration -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>

<ItemGroup>
<None Remove="DemaConsulting.SpdxModel.Tests.csproj.DotSettings" />
<None Remove="IO\Examples\SPDXJSONExample-v2.2.spdx.json" />
<None Remove="IO\Examples\SPDXJSONExample-v2.3.spdx.json" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="IO\Examples\SPDXJSONExample-v2.2.spdx.json" />
<EmbeddedResource Include="IO\Examples\SPDXJSONExample-v2.3.spdx.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.103">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.19.0.132793">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\DemaConsulting.SpdxModel\DemaConsulting.SpdxModel.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
<!-- Test Framework Dependencies -->
<ItemGroup>
<!-- coverlet.collector uses child-element form to configure both PrivateAssets and IncludeAssets:
- PrivateAssets="all" keeps this test-coverage tool out of any consuming project's dependencies.
- IncludeAssets lists all asset types (including 'build' and 'buildtransitive') to ensure the
data collector MSBuild targets are activated so coverage is collected during test runs. -->
<PackageReference Include="coverlet.collector" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
</ItemGroup>

<!-- Code Analysis Dependencies -->
<ItemGroup>
<!-- Analyzer packages use child-element form to configure both PrivateAssets and IncludeAssets:
- PrivateAssets="all" prevents these build-time analyzers from becoming transitive dependencies
in any project that references this test project.
- IncludeAssets lists all asset types (including 'analyzers' and 'buildtransitive') to ensure
Roslyn analyzers and MSBuild targets are fully activated during the build. -->
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.103">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.20.0.135146">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<!-- Project References -->
<ItemGroup>
<ProjectReference Include="..\..\src\DemaConsulting.SpdxModel\DemaConsulting.SpdxModel.csproj" />
</ItemGroup>

<ItemGroup>
<None Remove="DemaConsulting.SpdxModel.Tests.csproj.DotSettings" />
<None Remove="IO\Examples\SPDXJSONExample-v2.2.spdx.json" />
<None Remove="IO\Examples\SPDXJSONExample-v2.3.spdx.json" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="IO\Examples\SPDXJSONExample-v2.2.spdx.json" />
<EmbeddedResource Include="IO\Examples\SPDXJSONExample-v2.3.spdx.json" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>