diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..19e6f77
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,39 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+env:
+ DOTNET_NOLOGO: 1
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ name: Install Current .NET SDK
+ - name: Restore dependencies
+ run: dotnet restore /p:TreatWarningsAsErrors=true
+ - name: Build
+ run: dotnet build --configuration Release --no-restore /p:TreatWarningsAsErrors=true
+
+ nupkg:
+ name: Package
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ name: Install Current .NET SDK
+ - name: Generate NuGet Packages
+ run: dotnet pack --configuration Release --output nupkg /p:TreatWarningsAsErrors=true
+ - uses: actions/upload-artifact@v4
+ if: success() && github.ref == 'refs/heads/main'
+ with:
+ name: nupkg
+ path: nupkg/*
+ retention-days: 1
diff --git a/Polyadic.Build.SemanticVersioning/GenerateSemanticVersionRanges.cs b/Polyadic.Build.SemanticVersioning/GenerateSemanticVersionRanges.cs
new file mode 100644
index 0000000..7e4f698
--- /dev/null
+++ b/Polyadic.Build.SemanticVersioning/GenerateSemanticVersionRanges.cs
@@ -0,0 +1,45 @@
+using Microsoft.Build.Framework;
+using NuGet.Versioning;
+
+namespace Polyadic.Build.SemanticVersioning;
+
+public sealed class GenerateSemanticVersionRanges : Microsoft.Build.Utilities.Task
+{
+ [Required]
+ public ITaskItem[] ProjectReferencesWithVersions { get; set; } = null!;
+
+ public string VersionMetadataName { get; set; } = "ProjectVersion";
+
+ [Output]
+ public ITaskItem[] ProjectReferencesWithExactVersions { get; set; } = null!;
+
+ public override bool Execute()
+ {
+ foreach (var projectReference in ProjectReferencesWithVersions)
+ {
+ var versionString = projectReference.GetMetadata(VersionMetadataName);
+
+ if (!SemanticVersion.TryParse(versionString, out var version))
+ {
+ Log.LogError("'{0}' is not a valid semantic version", versionString);
+ return false;
+ }
+
+ projectReference.SetMetadata(VersionMetadataName, ToVersionRange(version));
+ }
+
+ ProjectReferencesWithExactVersions = ProjectReferencesWithVersions;
+ return true;
+ }
+
+ private static string ToVersionRange(SemanticVersion version)
+ => version switch
+ {
+ /* No SemVer guarantees for pre-release (e.g. nightly) versions, so we use an exact version. */
+ { IsPrerelease: true } => $"[{version}]",
+ /* We use Cargo's convention for 0.x versions i.e. minor = breaking, patch = feature or patch. */
+ { Major: 0, Minor: var minor, Patch: var patch } => $"[0.{minor}.{patch}, 0.{minor + 1})",
+ /* 1.x versions follow regular SemVer rules. */
+ { Major: var major, Minor: var minor, Patch: var patch } => $"[{major}.{minor}.{patch}, {major + 1})",
+ };
+}
diff --git a/Polyadic.Build.SemanticVersioning/Polyadic.Build.SemanticVersioning.csproj b/Polyadic.Build.SemanticVersioning/Polyadic.Build.SemanticVersioning.csproj
new file mode 100644
index 0000000..dba6458
--- /dev/null
+++ b/Polyadic.Build.SemanticVersioning/Polyadic.Build.SemanticVersioning.csproj
@@ -0,0 +1,52 @@
+
+
+ netstandard2.0
+ enable
+ enable
+ 13.0
+
+
+ 1.0.0
+ Uses semantic version ranges for ProjectReferences.
+ readme.md
+
+
+
+ true
+ true
+ $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
+ tasks
+ true
+ $(NoWarn);NU5100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Polyadic.Build.SemanticVersioning/buildMultiTargeting/Polyadic.Build.SemanticVersioning.props b/Polyadic.Build.SemanticVersioning/buildMultiTargeting/Polyadic.Build.SemanticVersioning.props
new file mode 100644
index 0000000..15c1ba0
--- /dev/null
+++ b/Polyadic.Build.SemanticVersioning/buildMultiTargeting/Polyadic.Build.SemanticVersioning.props
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/Polyadic.Build.SemanticVersioning/buildMultiTargeting/Polyadic.Build.SemanticVersioning.targets b/Polyadic.Build.SemanticVersioning/buildMultiTargeting/Polyadic.Build.SemanticVersioning.targets
new file mode 100644
index 0000000..9dd646b
--- /dev/null
+++ b/Polyadic.Build.SemanticVersioning/buildMultiTargeting/Polyadic.Build.SemanticVersioning.targets
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+ <_ProjectReferencesWithVersions Remove="@(_ProjectReferencesWithVersions)" />
+ <_ProjectReferencesWithVersions Include="@(_ProjectReferencesWithExactVersions)" />
+
+
+
diff --git a/Polyadic.Build.SemanticVersioning/readme.md b/Polyadic.Build.SemanticVersioning/readme.md
new file mode 100644
index 0000000..c4171c7
--- /dev/null
+++ b/Polyadic.Build.SemanticVersioning/readme.md
@@ -0,0 +1,8 @@
+# Polyadic.Build.SemanticVersioning
+
+> [!CAUTION]
+> This package is meant for consumption by Polyadic repositories.
+> As such, we may break it at any time.
+
+Generates semantic version ranges (e.g. `[1.2.0, 2)`) for project
+references during `dotnet pack`.
diff --git a/Polyadic.Build.sln b/Polyadic.Build.sln
index 58ea566..15d8e11 100644
--- a/Polyadic.Build.sln
+++ b/Polyadic.Build.sln
@@ -3,6 +3,22 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Polyadic.Build.SemanticVersioning", "Polyadic.Build.SemanticVersioning\Polyadic.Build.SemanticVersioning.csproj", "{394028FA-7997-45A2-B4E7-87852BAE577E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build Config", "Build Config", "{84545C75-8BA9-4802-AAE1-C45F0CD258C0}"
+ ProjectSection(SolutionItems) = preProject
+ global.json = global.json
+ Directory.Build.props = Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64E40113-89F8-49B0-B33E-4A99902944E1}"
+ ProjectSection(SolutionItems) = preProject
+ readme.md = readme.md
+ .gitignore = .gitignore
+ .gitattributes = .gitattributes
+ .editorconfig = .editorconfig
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -11,4 +27,10 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {394028FA-7997-45A2-B4E7-87852BAE577E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {394028FA-7997-45A2-B4E7-87852BAE577E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {394028FA-7997-45A2-B4E7-87852BAE577E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {394028FA-7997-45A2-B4E7-87852BAE577E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
EndGlobal
diff --git a/readme.md b/readme.md
index da45bc8..33ed4dd 100644
--- a/readme.md
+++ b/readme.md
@@ -2,6 +2,11 @@
Shared Build Tools for all Polyadic/Funcky projects.
+## Packages
+
+* **Polyadic.Build.SemanticVersioning**: Generates semantic version ranges for project references. \
+ [](https://www.nuget.org/packages/Polyadic.Build.SemanticVersioning)
+
## License
Licensed under either of