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);
+                }
+            }
+        }
+    }
+}

Reply via email to