Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Compatible with both SDK Pack and NuGetizer. Does not require NuGetizer.
- **Fragment resolution**: explicit `<!-- #fragment -->` 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/`).
Expand Down
21 changes: 13 additions & 8 deletions src/Readme/build/Readme.targets
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@
<PackageReadmeFile></PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Update="@(None -> WithMetadataValue('Filename', 'readme'))" Pack="false" />
<Content Update="@(Content -> WithMetadataValue('Filename', 'readme'))" Pack="false" />
<!-- Update="%(Identity)" + Condition: target-time Update="spec" matches every item on Unix. -->
<None Update="%(None.Identity)" Pack="false" Condition="'%(None.Filename)' == 'readme'" />
<Content Update="%(Content.Identity)" Pack="false" Condition="'%(Content.Filename)' == 'readme'" />
</ItemGroup>
</Target>

Expand Down Expand Up @@ -118,9 +119,9 @@
Condition="'$(IsPackable)' == 'true' and '$(PackReadme)' == 'true' and '$(PackageReadmeFile)' != ''">

<PropertyGroup>
<!-- Prefer project-directory readme as the source of includes. -->
<_ReadmeSourceFile Condition="'$(_ReadmeSourceFile)' == '' and Exists('$(MSBuildProjectDirectory)\$(PackageReadmeFile)')">$(MSBuildProjectDirectory)\$(PackageReadmeFile)</_ReadmeSourceFile>
<_ReadmeSourceFile Condition="'$(_ReadmeSourceFile)' == '' and Exists('$(PackageReadmeFile)')">$(PackageReadmeFile)</_ReadmeSourceFile>
<!-- Prefer project-directory readme as the source of includes. Normalize so FullPath compares work on Unix. -->
<_ReadmeSourceFile Condition="'$(_ReadmeSourceFile)' == '' and Exists('$(MSBuildProjectDirectory)\$(PackageReadmeFile)')">$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(PackageReadmeFile)'))</_ReadmeSourceFile>
<_ReadmeSourceFile Condition="'$(_ReadmeSourceFile)' == '' and Exists('$(PackageReadmeFile)')">$([MSBuild]::NormalizePath('$(PackageReadmeFile)'))</_ReadmeSourceFile>

<!-- Package path filename (readme.md by default). -->
<_ReadmePackagePath Condition="'$(_ReadmePackagePath)' == ''">$(PackageReadmeFilename)$(PackageReadmeExtension)</_ReadmePackagePath>
Expand Down Expand Up @@ -160,11 +161,15 @@
'readme' would also hit Readme.props / Readme.targets / Readme.dll).
-->
<ItemGroup Condition="Exists('$(_ReadmeProcessedFile)')">
<None Update="$(_ReadmeSourceFile)" Pack="false" Condition="'$(_ReadmeSourceFile)' != ''" />
<Content Update="$(_ReadmeSourceFile)" Pack="false" Condition="'$(_ReadmeSourceFile)' != ''" />
<!-- Update="%(Identity)" + FullPath: target-time Update="path" matches every None/Content on Unix (NU5030). -->
<None Update="%(None.Identity)" Pack="false"
Condition="'$(_ReadmeSourceFile)' != '' and '%(None.FullPath)' == '$(_ReadmeSourceFile)'" />
<Content Update="%(Content.Identity)" Pack="false"
Condition="'$(_ReadmeSourceFile)' != '' and '%(Content.FullPath)' == '$(_ReadmeSourceFile)'" />

<!-- Remove prior intermediate entries to avoid duplicates across incremental runs. -->
<None Remove="$(_ReadmeProcessedFile)" />
<_ReadmeProcessedNone Include="@(None)" Condition="'%(None.FullPath)' == '$(_ReadmeProcessedFile)'" />
<None Remove="@(_ReadmeProcessedNone)" />
<None Include="$(_ReadmeProcessedFile)"
Pack="true"
PackagePath="$(_ReadmePackagePath)"
Expand Down
45 changes: 39 additions & 6 deletions src/Tests/PackScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,50 @@ public void SdkPack_PackReadmeFalse_PacksWithoutReadme_NoNU5039()
var nupkg = Directory.GetFiles(Path.Combine(work, "out"), "*.nupkg").Single();
File.Copy(nupkg, Path.Combine(evidenceDir, Path.GetFileName(nupkg)), overwrite: true);

using (var zip = ZipFile.OpenRead(nupkg))
{
var entries = zip.Entries.Select(e => 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("<readme>", 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("<license type=\"file\">OSMFEULA.txt</license>", nuspec, StringComparison.Ordinal);
}

static IReadOnlyList<string> ZipEntries(string nupkg)
{
using var zip = ZipFile.OpenRead(nupkg);
return zip.Entries.Select(e => e.FullName).ToList();
}

string PrepareScenario(string name, string evidenceDir)
Expand Down
1 change: 1 addition & 0 deletions src/Tests/Scenarios/PackReadmeFalse/OSMFEULA.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OSMF EULA for PackReadme=false pack regression.
6 changes: 6 additions & 0 deletions src/Tests/Scenarios/PackReadmeFalse/PackReadmeFalse.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@
<ImportNuGetBuildTasksPackTargetsFromSdk>true</ImportNuGetBuildTasksPackTargetsFromSdk>
<!-- Opt out of automatic package readme packing (readme.md still present on disk). -->
<PackReadme>false</PackReadme>
<PackageLicenseFile>OSMFEULA.txt</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<Content Include="OSMFEULA.txt" Pack="true" PackagePath="OSMFEULA.txt" />
</ItemGroup>
</Project>

6 changes: 6 additions & 0 deletions src/Tests/Scenarios/SdkPackLicense/Class1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SdkPackLicense
{
public class Class1
{
}
}
1 change: 1 addition & 0 deletions src/Tests/Scenarios/SdkPackLicense/NOTICE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Notice file that must stay packed.
1 change: 1 addition & 0 deletions src/Tests/Scenarios/SdkPackLicense/OSMFEULA.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OSMF EULA for pack regression.
18 changes: 18 additions & 0 deletions src/Tests/Scenarios/SdkPackLicense/SdkPackLicense.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageId>SdkPackLicenseSample</PackageId>
<Version>1.0.0</Version>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
<NuGetize>false</NuGetize>
<ImportNuGetBuildTasksPackTargetsFromSdk>true</ImportNuGetBuildTasksPackTargetsFromSdk>
<PackageLicenseFile>OSMFEULA.txt</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<Content Include="OSMFEULA.txt" Pack="true" PackagePath="OSMFEULA.txt" />
<None Include="NOTICE.txt" Pack="true" PackagePath="NOTICE.txt" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions src/Tests/Scenarios/SdkPackLicense/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# SdkPackLicense sample
Loading