-
Notifications
You must be signed in to change notification settings - Fork 329
Use upper-bound version ranges for sibling package dependencies #4337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,29 +40,54 @@ | |
| <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled> | ||
| </PropertyGroup> | ||
|
|
||
| <!-- | ||
| Compute version ceilings for sibling packages. The ceiling is the next minor version | ||
| (prerelease suffix stripped) derived from the *floor* (XxxPackageVersion), giving a range | ||
| like [1.1.0-preview1-ci123, 1.2.0). This prevents NuGet from resolving an incompatible | ||
| newer minor/major version. | ||
| Because the ceiling is derived from the floor, no separate NextVersion lookup is needed | ||
| here — the range is self-contained regardless of what pipeline or developer scenario | ||
| computed the floor. | ||
| --> | ||
| <PropertyGroup> | ||
| <!-- SNI version (external package, version declared once here) --> | ||
| <SniVersion>6.0.2</SniVersion> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I decided to declare the SNI version once here rather than here and in the SqlClient nuspec. It's a pseudo-sibling package, so I felt if warranted this special treatement. |
||
| <SniVersionRange>[$(SniVersion), $(SniVersion.Split('.')[0]).$([MSBuild]::Add($(SniVersion.Split('.')[1]), 1)).0)</SniVersionRange> | ||
| </PropertyGroup> | ||
|
|
||
| <PropertyGroup Condition="'$(ReferenceType)' == 'Package'"> | ||
| <SqlServerVersionCeiling>$(SqlServerPackageVersion.Trim().Split('.')[0]).$([MSBuild]::Add($(SqlServerPackageVersion.Trim().Split('.')[1]), 1)).0</SqlServerVersionCeiling> | ||
| <LoggingVersionCeiling>$(LoggingPackageVersion.Trim().Split('.')[0]).$([MSBuild]::Add($(LoggingPackageVersion.Trim().Split('.')[1]), 1)).0</LoggingVersionCeiling> | ||
| <AbstractionsVersionCeiling>$(AbstractionsPackageVersion.Trim().Split('.')[0]).$([MSBuild]::Add($(AbstractionsPackageVersion.Trim().Split('.')[1]), 1)).0</AbstractionsVersionCeiling> | ||
| <SqlClientVersionCeiling>$(SqlClientPackageVersion.Trim().Split('.')[0]).$([MSBuild]::Add($(SqlClientPackageVersion.Trim().Split('.')[1]), 1)).0</SqlClientVersionCeiling> | ||
| <AzureVersionCeiling>$(AzurePackageVersion.Trim().Split('.')[0]).$([MSBuild]::Add($(AzurePackageVersion.Trim().Split('.')[1]), 1)).0</AzureVersionCeiling> | ||
| <AkvProviderVersionCeiling>$(AkvProviderPackageVersion.Trim().Split('.')[0]).$([MSBuild]::Add($(AkvProviderPackageVersion.Trim().Split('.')[1]), 1)).0</AkvProviderVersionCeiling> | ||
| </PropertyGroup> | ||
|
|
||
| <!-- ===================================================================== --> | ||
| <!-- Driver Packages --> | ||
|
|
||
| <!-- The driver packages need version numbers when we build via Package references. --> | ||
| <ItemGroup Condition="'$(ReferenceType)' == 'Package'"> | ||
| <PackageVersion | ||
| Include="Microsoft.SqlServer.Server" | ||
| Version="$(SqlServerPackageVersion)" /> | ||
| Version="[$(SqlServerPackageVersion), $(SqlServerVersionCeiling))" /> | ||
| <PackageVersion | ||
|
paulmedynski marked this conversation as resolved.
|
||
| Include="Microsoft.Data.SqlClient.Internal.Logging" | ||
| Version="$(LoggingPackageVersion)" /> | ||
| Version="[$(LoggingPackageVersion), $(LoggingVersionCeiling))" /> | ||
| <PackageVersion | ||
| Include="Microsoft.Data.SqlClient.Extensions.Abstractions" | ||
| Version="$(AbstractionsPackageVersion)" /> | ||
| Version="[$(AbstractionsPackageVersion), $(AbstractionsVersionCeiling))" /> | ||
| <PackageVersion | ||
| Include="Microsoft.Data.SqlClient" | ||
| Version="$(SqlClientPackageVersion)" /> | ||
| Version="[$(SqlClientPackageVersion), $(SqlClientVersionCeiling))" /> | ||
| <PackageVersion | ||
| Include="Microsoft.Data.SqlClient.Extensions.Azure" | ||
| Version="$(AzurePackageVersion)" /> | ||
| Version="[$(AzurePackageVersion), $(AzureVersionCeiling))" /> | ||
| <PackageVersion | ||
| Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" | ||
| Version="$(AkvProviderPackageVersion)" /> | ||
| Version="[$(AkvProviderPackageVersion), $(AkvProviderVersionCeiling))" /> | ||
| </ItemGroup> | ||
|
|
||
| <!-- ===================================================================== --> | ||
|
|
@@ -108,8 +133,8 @@ | |
| <!-- SqlClient Dependencies --> | ||
|
|
||
| <ItemGroup> | ||
| <PackageVersion Include="Microsoft.Data.SqlClient.SNI" Version="6.0.2" /> | ||
| <PackageVersion Include="Microsoft.Data.SqlClient.SNI.runtime" Version="6.0.2" /> | ||
| <PackageVersion Include="Microsoft.Data.SqlClient.SNI" Version="$(SniVersionRange)" /> | ||
| <PackageVersion Include="Microsoft.Data.SqlClient.SNI.runtime" Version="$(SniVersionRange)" /> | ||
| <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.16.0" /> | ||
| <PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.16.0" /> | ||
| <PackageVersion Include="System.Buffers" Version="4.6.1" /> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,30 @@ parameters: | |
| - detailed | ||
| - diagnostic | ||
|
|
||
| # The name of the Logging pipeline artifacts to download. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Abstractions and Azure build/pack CI jobs were missing some sibling package versions, and weren't using package-mode properly. |
||
| # | ||
| # This is used when the referenceType is 'Package'. | ||
| - name: loggingArtifactsName | ||
| type: string | ||
| default: Logging.Artifacts | ||
|
|
||
| # The Logging package version to depend on. | ||
| # | ||
| # This is used when the referenceType is 'Package'. | ||
| - name: loggingPackageVersion | ||
| type: string | ||
| default: '' | ||
|
|
||
| # The C# project reference type to use when building and packing the packages. | ||
| - name: referenceType | ||
| type: string | ||
| default: Project | ||
| values: | ||
| # Reference sibling packages as NuGet packages. | ||
| - Package | ||
| # Reference sibling packages as C# projects. | ||
| - Project | ||
|
|
||
| jobs: | ||
|
|
||
| - job: pack_abstractions_package_job | ||
|
|
@@ -102,21 +126,46 @@ jobs: | |
| - pwsh: 'Get-ChildItem Env: | Sort-Object Name' | ||
| displayName: '[Debug] Print Environment Variables' | ||
|
|
||
| # For Package reference builds, we must first download the dependency | ||
| # package artifacts. | ||
| - ${{ if eq(parameters.referenceType, 'Package') }}: | ||
| - task: DownloadPipelineArtifact@2 | ||
| displayName: Download Logging Package Artifacts | ||
| inputs: | ||
| artifactName: ${{ parameters.loggingArtifactsName }} | ||
| targetPath: $(Build.SourcesDirectory)/packages | ||
|
|
||
| # Install the .NET SDK. | ||
| - template: /eng/pipelines/steps/install-dotnet.yml@self | ||
| parameters: | ||
| debug: ${{ parameters.debug }} | ||
|
|
||
| # Create the NuGet packages. | ||
| - task: DotNetCoreCLI@2 | ||
| displayName: Create NuGet Package | ||
| inputs: | ||
| command: pack | ||
| packagesToPack: $(project) | ||
| configurationToPack: ${{ parameters.buildConfiguration }} | ||
| packDirectory: $(dotnetPackagesDir) | ||
| verbosityToPack: ${{ parameters.dotnetVerbosity }} | ||
| buildProperties: AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }};AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }} | ||
| # | ||
| # When referenceType is Package, we must pass ReferenceType and the | ||
| # dependency version so that Directory.Packages.props applies version | ||
| # ranges to sibling package dependencies. | ||
| - ${{ if eq(parameters.referenceType, 'Package') }}: | ||
| - task: DotNetCoreCLI@2 | ||
| displayName: Create NuGet Package | ||
| inputs: | ||
| command: pack | ||
| packagesToPack: $(project) | ||
| configurationToPack: ${{ parameters.buildConfiguration }} | ||
| packDirectory: $(dotnetPackagesDir) | ||
| verbosityToPack: ${{ parameters.dotnetVerbosity }} | ||
| buildProperties: AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }};AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }};ReferenceType=Package;LoggingPackageVersion=${{ parameters.loggingPackageVersion }} | ||
|
|
||
| - ${{ else }}: | ||
| - task: DotNetCoreCLI@2 | ||
| displayName: Create NuGet Package | ||
| inputs: | ||
| command: pack | ||
| packagesToPack: $(project) | ||
| configurationToPack: ${{ parameters.buildConfiguration }} | ||
| packDirectory: $(dotnetPackagesDir) | ||
| verbosityToPack: ${{ parameters.dotnetVerbosity }} | ||
| buildProperties: AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }};AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }} | ||
|
|
||
| # Publish the NuGet packages as a named pipeline artifact. | ||
| - task: PublishPipelineArtifact@1 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ceiling should be the next major version that can contain breaking changes.
Per SemVer versioning semantics, a minor release is never expected to contain breaking changes for any consumers, and should be backwards-compatible with consuming applications if customers happen to upgrade.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I thought about that, but do we actually want to allow dependency across the minor version boundary? Do we intend to release Abstractions 1.1.0 without releasing Azure 1.1.0, for example?
This is a policy question:
@benrr101 @mdaigle - What are your thoughts?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually thinking that we don't need to set upper bounds at all. What is the actual use case for explicitly limiting? Why is our suite special in a way that requires upper bounds when the ecosystem discourages them? When nuget goes to resolve the dependency graph, it will already select the lowest version that satisfies all constraints in the graph. This means that as long as we continue to set exact lower bounds, customers will not get even minor version bumps of sibling packages except in certain special situations mentioned below.
Consider:
If we publish Abstractions 1.1.0 (or even 2.0.0), the app will still resolve to 1.0.0.
The only ways a customer could get an incompatible version of a sibling package is:
(Can expose customers to behavior changes or runtime errors)
(gives a nuget warning that direct MDS 7.0.0 dependency is a downgrade from 8.0.0)
(can fail at runtime)
The core of my point is that our risks aren't special. They're the same risks any other library has. The ecosystem puts the burden on users to safely manage their package versions. In exchange, it's almost always possible to resolve the package graph without irreconcilable version constraints. Avoiding upper bounds maintains a safety hatch for customers. In case they want/need to take a higher version of a sibling package, they can do so at their own risk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @mdaigle - why is MDS special from any other nuget package, and constraining is highly discouraged
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a little more digging myself:
Industry norm: bare minimum versions (1.2.3 meaning >= 1.2.3). This is what the vast majority of NuGet packages ship with, including most Microsoft packages. The reasoning:
Bounded ranges are rare but recommended for tightly-coupled packages. Microsoft's own guidance (the NuGet docs on version ranges) says:
Who uses bounded ranges in practice:
We're definitely in the "tightly coupled sibling" case, but does that mean we absolutely need upper bounds? Our EF Core friends use it, so perhaps a discussion with them is warranted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Tightly coupled siblings or plugins where a major bump is breaking" - interesting!
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That language is an editorialization from the 🤖. The strongest statement I see is:
But it is interesting to see that EFCore does use upper bounds. I think the mitigating circumstances are that we're not widely consumed by other libraries and we do own the other packages, so the chance of negatively impacting version resolution is probably very small. Also curious to hear if EFCore has ever run into any issues.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdaigle I think its important to think about the usecase that we have today and that is the Azure extension Package that will no longer be backwards compatible with MDS 7.0.x if this PR makes in: #4200.
How would you expect customers to know to not use a future version of Azure extension package (2.x) with MDS 7.0 (because it would change the dependency tree massively and require upgrading all related application dependencies for some customers) but it is compatible with MDS 7.1??
Azure extension is not a driver's transitive dependency but an extension package that customers would themselves reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why I'd like to use a different versioning scheme. If all the packages are versioned together then there's no ambiguity.
I think I'm ok to proceed with this. Paul's examples show that there is precedence for the pattern. I wanted to make sure that we don't have special and unexpected behavior.