Central Package Management
Why PCPM leans on Microsoft's CPM, not against it.
Central Package Management is a feature of the .NET SDK that
moves package versions out of individual .csproj files and into a
single Directory.Packages.props at the root of the workspace. PCPM
is built on top of CPM rather than against it; this page explains
why and how to make the most of it.
The CPM file
A Directory.Packages.props is a normal MSBuild .props file. It
sits in the workspace root and is auto-imported by every .csproj
under the same directory tree. Its job is to declare
<PackageVersion /> entries:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
The <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
property is what makes this work — it tells MSBuild that the
version for any <PackageReference /> should come from this file
rather than the .csproj.
A <PackageReference /> in any .csproj then looks like:
<ItemGroup>
<PackageReference Include="Serilog" />
</ItemGroup>
No Version attribute. The version is implicit; the .csproj only
knows the id.
Why this is the right substrate for PCPM
The most common complaint about package managers is “version drift
across projects.” With CPM, that complaint is structurally
impossible: there is one place to put a version, and it applies
everywhere. The fact that PCPM writes the version for you when you
run pcpm add is a convenience, not a requirement — you can
hand-edit Directory.Packages.props and pcpm install will pick
up the change.
The second reason is that CPM is already a MSBuild feature. The
.NET SDK, Visual Studio, Rider, and dotnet restore all understand
it. PCPM doesn’t have to ship a build target, a custom resolver,
or a NuGet.Protocol plugin. It just writes CPM files.
The third reason is that the .NET community has settled on CPM
as the answer to the “many csproj” problem. By leaning on it,
PCPM inherits the solution that’s already winning. By building
against it, PCPM gets to focus on the parts that CPM doesn’t
solve: the store, the hardlinks, the strict resolution.
The CPM-friendly file model
PCPM touches only three files in your workspace:
Directory.Packages.props— written bypcpm add,pcpm remove,pcpm convert. Never overwritten if it already exists; PCPM adopts existing entries.pcpm.json— written bypcpm init. The PCPM manifest. You almost never edit this by hand.pcpm.lock— written bypcpm install. The resolved graph with content hashes. Generated, never hand-edited.
The .csproj files are touched by pcpm add and pcpm remove
to add or remove <PackageReference /> entries, and by
pcpm convert to strip the Version attribute (in the
case where you weren’t using CPM before).
The MSBuild .targets files PCPM ships (pcpm.MsBuild.targets)
are pulled in automatically via the pcpm.MsBuild NuGet package.
This package contains a RelinkBin task that re-hardlinks the
output of dotnet build to the store, so the binlog on CI
doesn’t grow with each build. The package is opt-in; install
it once per workspace if you want this behavior.
Edge cases
- Per-project version overrides. You can override the CPM
version for a single project with
<PackageReference Include="X" Version="1.2.3" OverrideCentralPackageVersions="true" />. PCPM respects this; it just adds the override to the resolution graph as a tighter constraint. - Per-TFM versions.
<PackageVersion Include="X" Version="1.0.0"><Conditions><TargetFramework>net48</TargetFramework></Conditions></PackageVersion>is supported. PCPM doesn’t ship a UI for it; hand-edit the.propsif you need it. - Private feeds. If you have a
NuGet.configwith<packageSource>, PCPM picks it up and writes the same feeds intopcpm.json.
See also
- Quick start — see CPM in action in three commands.
- Migrating from CPM —
pcpm convertadopts your existing CPM layout. - Configuration — every
pcpm.jsonoption.