diff --git a/AGENTS.md b/AGENTS.md
index 69dde55..4e944d0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -35,7 +35,7 @@ Compatible with both SDK Pack and NuGetizer. Does not require NuGetizer.
- **Fragment resolution**: explicit `` pairs win (placement controls whether a section title is included); otherwise GitHub heading auto-anchors match and **include the heading line** (e.g. `## Usage` for `#usage`).
- **Token replacement**: after includes, `$token$` placeholders are replaced via `@(PackageReplacementToken)` (official NuGet: Id/Version/Author/Title/Description/Copyright/Configuration; plus Authors and Product; consumer-extensible). Case-insensitive names; **last value wins** for duplicates (never fails). Same item name as NuGetizer for coexistence; newer NuGetizers can adopt this model.
- **`CollectReplacementTokens`**: populates default `@(PackageReplacementToken)` items. Depend on it before using tokens. Consumers add items anytime; `AfterTargets="CollectReplacementTokens"` to remove/replace defaults; `$(CollectReplacementTokensDependsOn)` / `BeforeTargets` for pre-work.
-- **`ProcessPackageReadme`**: pack-time target (`DependsOnTargets=CollectReplacementTokens;$(ProcessPackageReadmeDependsOn)`) runs the process task then retargets pack items to the intermediate file. Does **not** warn on leftover `$token$` (readme may document that syntax).
+- **`ProcessPackageReadme`**: pack-time target (`DependsOnTargets=CollectReplacementTokens;$(ProcessPackageReadmeDependsOn)`) runs the process task then retargets pack items to the intermediate file. Does **not** warn on leftover `$token$` (readme may document that syntax). Target-time `Update`/`Remove` must use `%(Identity)` + a `Condition` (or Remove of a filtered item list); `Update="path"` matches every `None`/`Content` item on Unix and clears `Pack` on license files (NU5030).
- **`ReplacePackageTokens`**: public task for arbitrary InputFile→OutputFile replacement with `Tokens="@(PackageReplacementToken)"`. Emits **RDM001** for remaining unknown placeholders (suppress via `NoWarn`). Suitable for incremental targets (`Inputs`/`Outputs`).
- **GitHub relative URLs**: after tokens, relative Markdown links/images are rewritten to `https://raw.githubusercontent.com/{owner}/{repo}/{commit}/…` when `ReadmeExpandGitHubUrls` is true (default), `RepositoryUrl` is a github.com absolute URL, and a commit is available (`RepositoryCommit` → `RepositorySha` → `SourceRevisionId`). Auto-skips otherwise. Opt out with `ReadmeExpandGitHubUrls=false`. Uses Markdig `LinkInline` + `NormalizeRenderer` (same as NuGetizer).
- **ILRepack Markdig**: Markdig and its polyfill deps are internalized into `build/Readme.dll` so the package ships a single task assembly (no satellite DLLs under `build/`).
diff --git a/src/Readme/build/Readme.targets b/src/Readme/build/Readme.targets
index a34c22c..abc1773 100644
--- a/src/Readme/build/Readme.targets
+++ b/src/Readme/build/Readme.targets
@@ -81,8 +81,9 @@
-
-
+
+
+
@@ -118,9 +119,9 @@
Condition="'$(IsPackable)' == 'true' and '$(PackReadme)' == 'true' and '$(PackageReadmeFile)' != ''">
-
- <_ReadmeSourceFile Condition="'$(_ReadmeSourceFile)' == '' and Exists('$(MSBuildProjectDirectory)\$(PackageReadmeFile)')">$(MSBuildProjectDirectory)\$(PackageReadmeFile)
- <_ReadmeSourceFile Condition="'$(_ReadmeSourceFile)' == '' and Exists('$(PackageReadmeFile)')">$(PackageReadmeFile)
+
+ <_ReadmeSourceFile Condition="'$(_ReadmeSourceFile)' == '' and Exists('$(MSBuildProjectDirectory)\$(PackageReadmeFile)')">$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(PackageReadmeFile)'))
+ <_ReadmeSourceFile Condition="'$(_ReadmeSourceFile)' == '' and Exists('$(PackageReadmeFile)')">$([MSBuild]::NormalizePath('$(PackageReadmeFile)'))
<_ReadmePackagePath Condition="'$(_ReadmePackagePath)' == ''">$(PackageReadmeFilename)$(PackageReadmeExtension)
@@ -160,11 +161,15 @@
'readme' would also hit Readme.props / Readme.targets / Readme.dll).
-->
-
-
+
+
+
-
+ <_ReadmeProcessedNone Include="@(None)" Condition="'%(None.FullPath)' == '$(_ReadmeProcessedFile)'" />
+
e.FullName).ToList();
- File.WriteAllLines(Path.Combine(evidenceDir, "nupkg-entries.txt"), entries);
- Assert.DoesNotContain(entries, e => e.Equals("readme.md", StringComparison.OrdinalIgnoreCase));
- }
+ var entries = ZipEntries(nupkg);
+ File.WriteAllLines(Path.Combine(evidenceDir, "nupkg-entries.txt"), entries);
+ Assert.DoesNotContain(entries, e => e.Equals("readme.md", StringComparison.OrdinalIgnoreCase));
+ Assert.Contains(entries, e => e.Equals("OSMFEULA.txt", StringComparison.Ordinal));
var nuspec = ReadPackageEntry(nupkg, "PackReadmeFalseSample.nuspec");
File.WriteAllText(Path.Combine(evidenceDir, "package.nuspec"), nuspec);
Assert.DoesNotContain("", nuspec, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("NU5039", File.ReadAllText(packLog), StringComparison.OrdinalIgnoreCase);
+ Assert.DoesNotContain("NU5030", File.ReadAllText(packLog), StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ public void SdkPack_DoesNotClearPackOnLicenseOrOtherNoneItems()
+ {
+ var evidenceDir = Path.Combine(ScratchRoot, "sdk-pack-license");
+ Directory.CreateDirectory(evidenceDir);
+
+ var work = PrepareScenario("SdkPackLicense", evidenceDir);
+ var packed = EnsureReadmePackage(evidenceDir);
+ InjectLocalFeedAndReference(work, packed, useNuGetizer: false);
+
+ var packLog = Path.Combine(evidenceDir, "pack.log");
+ var exit = RunDotnet(
+ $"pack \"{Path.Combine(work, "SdkPackLicense.csproj")}\" -c Release -o \"{Path.Combine(work, "out")}\" -v:n",
+ work, packLog);
+ Assert.True(exit == 0, $"SDK pack with license file failed (NU5030?). See {packLog}\n{File.ReadAllText(packLog)}");
+
+ var nupkg = Directory.GetFiles(Path.Combine(work, "out"), "*.nupkg").Single();
+ var entries = ZipEntries(nupkg);
+ File.WriteAllLines(Path.Combine(evidenceDir, "nupkg-entries.txt"), entries);
+ Assert.Contains(entries, e => e.Equals("OSMFEULA.txt", StringComparison.Ordinal));
+ Assert.Contains(entries, e => e.Equals("NOTICE.txt", StringComparison.Ordinal));
+ Assert.Contains(entries, e => e.Equals("readme.md", StringComparison.OrdinalIgnoreCase));
+ Assert.DoesNotContain("NU5030", File.ReadAllText(packLog), StringComparison.OrdinalIgnoreCase);
+
+ var nuspec = ReadPackageEntry(nupkg, "SdkPackLicenseSample.nuspec");
+ Assert.Contains("OSMFEULA.txt", nuspec, StringComparison.Ordinal);
+ }
+
+ static IReadOnlyList ZipEntries(string nupkg)
+ {
+ using var zip = ZipFile.OpenRead(nupkg);
+ return zip.Entries.Select(e => e.FullName).ToList();
}
string PrepareScenario(string name, string evidenceDir)
diff --git a/src/Tests/Scenarios/PackReadmeFalse/OSMFEULA.txt b/src/Tests/Scenarios/PackReadmeFalse/OSMFEULA.txt
new file mode 100644
index 0000000..4d2fdf8
--- /dev/null
+++ b/src/Tests/Scenarios/PackReadmeFalse/OSMFEULA.txt
@@ -0,0 +1 @@
+OSMF EULA for PackReadme=false pack regression.
diff --git a/src/Tests/Scenarios/PackReadmeFalse/PackReadmeFalse.csproj b/src/Tests/Scenarios/PackReadmeFalse/PackReadmeFalse.csproj
index 4c127c2..f3f28f3 100644
--- a/src/Tests/Scenarios/PackReadmeFalse/PackReadmeFalse.csproj
+++ b/src/Tests/Scenarios/PackReadmeFalse/PackReadmeFalse.csproj
@@ -12,5 +12,11 @@
true
false
+ OSMFEULA.txt
+ true
+
+
+
+
diff --git a/src/Tests/Scenarios/SdkPackLicense/Class1.cs b/src/Tests/Scenarios/SdkPackLicense/Class1.cs
new file mode 100644
index 0000000..876c902
--- /dev/null
+++ b/src/Tests/Scenarios/SdkPackLicense/Class1.cs
@@ -0,0 +1,6 @@
+namespace SdkPackLicense
+{
+ public class Class1
+ {
+ }
+}
diff --git a/src/Tests/Scenarios/SdkPackLicense/NOTICE.txt b/src/Tests/Scenarios/SdkPackLicense/NOTICE.txt
new file mode 100644
index 0000000..5fe1f48
--- /dev/null
+++ b/src/Tests/Scenarios/SdkPackLicense/NOTICE.txt
@@ -0,0 +1 @@
+Notice file that must stay packed.
diff --git a/src/Tests/Scenarios/SdkPackLicense/OSMFEULA.txt b/src/Tests/Scenarios/SdkPackLicense/OSMFEULA.txt
new file mode 100644
index 0000000..20eea7f
--- /dev/null
+++ b/src/Tests/Scenarios/SdkPackLicense/OSMFEULA.txt
@@ -0,0 +1 @@
+OSMF EULA for pack regression.
diff --git a/src/Tests/Scenarios/SdkPackLicense/SdkPackLicense.csproj b/src/Tests/Scenarios/SdkPackLicense/SdkPackLicense.csproj
new file mode 100644
index 0000000..5a2fc44
--- /dev/null
+++ b/src/Tests/Scenarios/SdkPackLicense/SdkPackLicense.csproj
@@ -0,0 +1,18 @@
+
+
+ netstandard2.0
+ true
+ SdkPackLicenseSample
+ 1.0.0
+ false
+ false
+ false
+ true
+ OSMFEULA.txt
+ true
+
+
+
+
+
+
diff --git a/src/Tests/Scenarios/SdkPackLicense/readme.md b/src/Tests/Scenarios/SdkPackLicense/readme.md
new file mode 100644
index 0000000..7a5e6df
--- /dev/null
+++ b/src/Tests/Scenarios/SdkPackLicense/readme.md
@@ -0,0 +1 @@
+# SdkPackLicense sample