This is an automated email from the ASF dual-hosted git repository.
mgrigorov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/main by this push:
new 5df0055b10 Ensure nuget packages expose license via SPDX expression
(#3580)
5df0055b10 is described below
commit 5df0055b107ac7725fa94cf83de33880fe380cd3
Author: Kent Chenery <[email protected]>
AuthorDate: Tue Dec 2 03:39:28 2025 +1300
Ensure nuget packages expose license via SPDX expression (#3580)
---
lang/csharp/common.props | 2 +-
lang/csharp/src/apache/test/NuGetPackageTests.cs | 156 +++++++++++++++++++++++
2 files changed, 157 insertions(+), 1 deletion(-)
diff --git a/lang/csharp/common.props b/lang/csharp/common.props
index e12601f3d1..f41617eadf 100644
--- a/lang/csharp/common.props
+++ b/lang/csharp/common.props
@@ -48,7 +48,7 @@
<!-- Reference:
https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#pack-target -->
<Copyright>Copyright © 2019 The Apache Software Foundation.</Copyright>
<PackageIcon>logo.png</PackageIcon>
- <PackageLicenseFile>LICENSE</PackageLicenseFile>
+ <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://avro.apache.org/</PackageProjectUrl>
<PackageTags>Avro;Apache;Serialization;Binary;Json;Schema</PackageTags>
diff --git a/lang/csharp/src/apache/test/NuGetPackageTests.cs
b/lang/csharp/src/apache/test/NuGetPackageTests.cs
new file mode 100644
index 0000000000..493cede931
--- /dev/null
+++ b/lang/csharp/src/apache/test/NuGetPackageTests.cs
@@ -0,0 +1,156 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Xml.Linq;
+using NUnit.Framework;
+
+namespace Avro.Test.Utils
+{
+ [TestFixture]
+ public class NuGetPackageTests
+ {
+ private static readonly string[] PackageIds = new[]
+ {
+ "Apache.Avro",
+ "Apache.Avro.Tools",
+ "Apache.Avro.File.Snappy",
+ "Apache.Avro.File.BZip2",
+ "Apache.Avro.File.XZ",
+ "Apache.Avro.File.Zstandard"
+ };
+
+ [TestCaseSource(nameof(PackageIds))]
+ public void PackageContainsSpdxLicenseExpression(string packageId)
+ {
+ var nupkgPath = FindPackageInBuildOutput(packageId);
+ if (nupkgPath == null)
+ {
+ Assert.Inconclusive($"Package {packageId} not found. Run
'dotnet pack --configuration Release' first.");
+ return;
+ }
+
+ var nuspecXml = ExtractNuspecFromPackage(nupkgPath);
+
+ // Get the namespace from the root element
+ var ns = nuspecXml.Root?.Name.Namespace ?? XNamespace.None;
+ var licenseElement = nuspecXml.Root?.Element(ns +
"metadata")?.Element(ns + "license");
+
+ Assert.That(licenseElement, Is.Not.Null,
+ $"Package {packageId} does not contain a license element");
+
+ var licenseType = licenseElement?.Attribute("type")?.Value;
+ Assert.That(licenseType, Is.EqualTo("expression"),
+ $"Package {packageId} license type should be 'expression', but
was '{licenseType}'");
+
+ var licenseValue = licenseElement?.Value;
+ Assert.That(licenseValue, Is.EqualTo("Apache-2.0"),
+ $"Package {packageId} should have SPDX license expression
'Apache-2.0', but was '{licenseValue}'");
+ }
+
+ [TestCaseSource(nameof(PackageIds))]
+ public void PackageContainsLicenseFile(string packageId)
+ {
+ var nupkgPath = FindPackageInBuildOutput(packageId);
+ if (nupkgPath == null)
+ {
+ Assert.Inconclusive($"Package {packageId} not found. Run
'dotnet pack --configuration Release' first.");
+ return;
+ }
+
+ using (var archive = ZipFile.OpenRead(nupkgPath))
+ {
+ var licenseEntry = archive.Entries.FirstOrDefault(e =>
+ e.FullName.Equals("LICENSE",
StringComparison.OrdinalIgnoreCase));
+
+ Assert.That(licenseEntry, Is.Not.Null,
+ $"Package {packageId} does not contain LICENSE file");
+
+ Assert.That(licenseEntry.Length, Is.GreaterThan(0),
+ $"Package {packageId} LICENSE file is empty");
+ }
+ }
+
+ [TestCaseSource(nameof(PackageIds))]
+ public void PackageLicenseFileContainsApacheLicense(string packageId)
+ {
+ var nupkgPath = FindPackageInBuildOutput(packageId);
+ if (nupkgPath == null)
+ {
+ Assert.Inconclusive($"Package {packageId} not found. Run
'dotnet pack --configuration Release' first.");
+ return;
+ }
+
+ using (var archive = ZipFile.OpenRead(nupkgPath))
+ {
+ var licenseEntry = archive.Entries.FirstOrDefault(e =>
+ e.FullName.Equals("LICENSE",
StringComparison.OrdinalIgnoreCase));
+
+ Assert.That(licenseEntry, Is.Not.Null);
+
+ using (var stream = licenseEntry.Open())
+ using (var reader = new StreamReader(stream))
+ {
+ var content = reader.ReadToEnd();
+ Assert.That(content, Does.Contain("Apache License"),
+ $"Package {packageId} LICENSE file does not contain
Apache License text");
+ Assert.That(content, Does.Contain("Version 2.0"),
+ $"Package {packageId} LICENSE file does not specify
Version 2.0");
+ }
+ }
+ }
+
+ private string FindPackageInBuildOutput(string packageId)
+ {
+ // Find the lang/csharp root (4 levels up from test binary
directory: bin/Release/net8.0 -> test -> apache -> src -> csharp)
+ var testDir = TestContext.CurrentContext.TestDirectory;
+ var csharpRoot = Path.GetFullPath(Path.Combine(testDir, "..",
"..", "..", "..", ".."));
+
+ // Search for the package in Release output directories
+ var pattern = $"{packageId}.*.nupkg";
+ var files = Directory.GetFiles(csharpRoot, pattern,
SearchOption.AllDirectories)
+ .Where(f =>
f.Contains($"{Path.DirectorySeparatorChar}Release{Path.DirectorySeparatorChar}"))
+ .OrderByDescending(f => System.IO.File.GetLastWriteTime(f))
+ .ToArray();
+
+ return files.Length > 0 ? files[0] : null;
+ }
+
+ private XDocument ExtractNuspecFromPackage(string nupkgPath)
+ {
+ using (var archive = ZipFile.OpenRead(nupkgPath))
+ {
+ var nuspecEntry = archive.Entries.FirstOrDefault(e =>
+ e.FullName.EndsWith(".nuspec",
StringComparison.OrdinalIgnoreCase));
+
+ if (nuspecEntry == null)
+ {
+ Assert.Fail($"No .nuspec file found in package:
{nupkgPath}");
+ }
+
+ using (var stream = nuspecEntry.Open())
+ {
+ return XDocument.Load(stream);
+ }
+ }
+ }
+ }
+}