We’re happy to announce the first preview release of Package Source Mapping with Visual Studio 2022 preview 4! Package Source Mapping gives you fine-grained control of where your packages come from by mapping every package in your solution to a target package source.
Safeguarding your software supply chain is crucial if you use a mix of public and private package sources. Earlier this year, we published a set of best security practices to minimize risk from vulnerable packages and supply chain compromises like dependency confusion. Package Source Mapping is another powerful feature to help you fortify your supply chain against attacks. Use Source Mapping to centrally declare which source each package in your solution should restore from in your nuget.config
file.
Using Package Source Mapping
This feature is compatible with the following tools:
- Visual Studio 2022 preview 4 and later
- .NET SDK 6.0.100-rc.1 and later
- nuget.exe 6.0.0-preview.4 and later
Older tooling will ignore the Package Source Mapping configuration. To use this feature, ensure all your build environments use compatible tooling versions.
Package Source Mappings will apply to all project types – including .NET Framework – as long as compatible tooling is used for build and restore.
Package Source Mapping example configuration
To opt into this feature, you must have a nuget.config
file. Having a single nuget.config
at the root of your repository is considered a best practice and can be generated using the dotnet new nugetconfig
command. See our nuget.config documentation to learn more.
To get started, declare your desired package sources in your nuget.config
file. Following your source declarations, add a <packageSourceMapping>
element that specifies the desired mappings for each source.
In our example scenario, I declare two sources: nuget.org
and contoso.com
. I want to make sure my Contoso.*
packages only restore from contoso.com
while everything else comes from nuget.org
. The following configuration accomplishes this:
<!-- Define a global packages folder for your repository. -->
<!-- This is where installed packages will be stored locally. -->
<config>
<add key="globalPackagesFolder" value="globalPackagesFolder" />
</config>
<!-- Define my package sources, nuget.org and contoso.com. -->
<!-- `clear` ensures no additional sources are inherited from another config file. -->
<packageSources>
<clear />
<!-- `key` can be any identifier for your source. -->
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="contoso.com" value="https://contoso.com/packages/" />
</packageSources>
<!-- Define mappings by adding package ID patterns beneath the target source. -->
<!-- Contoso.* packages will be restored from contoso.com, everything else from nuget.org. -->
<packageSourceMapping>
<!-- key value for <packageSource> should match key values from <packageSources> element -->
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="contoso.com">
<package pattern="Contoso.*" />
</packageSource>
</packageSourceMapping>
You may notice that a package ID beginning with the Contoso.*
prefix technically matches both the Contoso.*
and *
patterns. Contoso.*
packages will be reliably restored from contoso.com
because the most specific matching patterns have precedence. See the Package Source Mapping rules to learn about the precedence rules and more.
Setting default sources
The *
pattern in the example configuration makes nuget.org
my default source – meaning any package that doesn’t match other specified patterns will be restored from nuget.org
without throwing an error. This configuration is advantageous if I primarily use packages from nuget.org
, only have a few internal packages, or use standard prefixes for all internal packages like Contoso.*
.
If your team doesn’t use standard prefixes for internal package IDs or vets nuget.org
packages prior to installation, then making a private source the default will suit your needs better. To make my private source contoso.com
the default and only allow Microsoft.*
packages from nuget.org
, I can use the following configuration:
<!-- Microsoft.* packages will only come from nuget.org. -->
<!-- All other packages come from contoso.com by default. -->
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="Microsoft.*" />
</packageSource>
<packageSource key="contoso.com">
<package pattern="*" />
</packageSource>
</packageSoureMapping>
Package Source Mapping rules
-
Two types of package ID patterns are supported:
a.
NuGet.*
– Package prefixes. Must end with a*
, which may match 0 or more characters.*
is the broadest valid prefix that matches all package IDs, but will have the lowest precedence by default.NuGet*
is also valid and will match package IDsNuGet
,NuGetFoo
, andNuGet.Bar
.b.
NuGet.Common
– Exact package IDs. -
Any requested package ID must map to one or more sources by matching a defined package ID pattern. In other words, once you have defined a
packageSourceMapping
element you must explicitly define which sources every package – including transitive packages – will be restored from.a. Both top-level (directly installed) and transitive packages must match defined patterns. There is no requirement that a top level package and its dependencies come from the same source.
b. The same ID pattern can be defined on multiple sources, allowing matching package IDs to be restored from any of the feeds that define the pattern. However, this isn’t recommended due to the impact on restore predictability (a given package could come from multiple sources).
-
When multiple unique patterns match a package ID, the most specific (longest) match will be preferred.
a. Exact package ID patterns always have the highest precedence while the generic
*
always has the lowest precedence. For an example package IDNuGet.Common
, the following package ID patterns are ordered from highest to lowest precedence:NuGet.Common
,NuGet.*
,*
. -
Package Source Mapping settings are applied following nuget.config precedence rules when multiple
nuget.config
files at various levels (machine-level, user-level, repo-level) are present.
Important: When the requested package already exists in the global packages folder, no source look-up will happen and the mappings will be ignored. Declare a global packages folder for your repo to gain the full security benefits of this feature. Work to improve the experience with the default global packages folder in planned for a next iteration.
We’ve curated several example scenarios to help you get a better understand of how these rules work and how to best leverage Package Source Mapping for your needs!
Get started
To get started with Package Source Mapping, download your tooling of choice:
- Visual Studio 2022 preview 4 and later
- .NET SDK 6.0.100-rc.1 and later
- nuget.exe 6.0.0-preview.4 and later
To fully onboard your repository take the following steps:
- Declare a new global packages folder for your repo.
- Run
dotnet list package --include-transitive
to view all top-level and transitive packages in your solution.- For .NET framework projects using
packages.config
, thepackages.config
file will have a flat list of all direct and transitive packages.
- For .NET framework projects using
- Define mappings such that every package ID in your solution – including transitive packages – matches a package ID pattern for the target source.
- Run restore to validate that you have configured your mappings correctly. If your mappings don’t fully cover every package ID in your solution, the error messages will help you identify the issue.
- When restore succeeds, you are done! Optionally consider:
- simplifying the configuration to fewer declarations by using broader package ID prefixes or setting a default source where possible.
- verifying the source each package was restored from by checking the metadata files in the global packages folder or reviewing the restore logs.
For an idea of how your source mappings may look like, refer to our samples repo.
We want to hear your feedback!
You can provide feedback for this feature at NuGet/Home!
For more general NuGet feedback and suggestions:
- Check out our documentation on submitting bugs and suggestions.
- Schedule a time to talk to NuGet.
- Reach out to us on Twitter – mention @nuget in your tweets.
Hello Nikolche, I configured and it works as expected, it seems it was backported to the .Net 5. I found an issue with a project using git submodules. The root and a child solution have both the with (they share some package sources). When I exec it wail with the following error . When I delete from the child solution it works again.
I have no idea why it wants...