Skip to content
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

[Pack] Handle empty Version tag in nuspec error #6092

Merged
merged 6 commits into from
Nov 4, 2024

Conversation

martinrrm
Copy link
Contributor

@martinrrm martinrrm commented Oct 9, 2024

Bug

Fixes: NuGet/Home#7987

Context of the bug

When running nuget pack with a .nuspec file that contains an empty version and passing -Version on the command line, nuget will fail with the error:

An error occured while trying to parse the value '' of property 'version' in the manifest file.
Value cannot be null or an empty string.
Parameter name: value

However, you put in a bogus version identifier in the nuspec file, the same command will run happily, with the version number passed on the command line.

Description

After having conversations, right now the solution is to override the nuspec version with the CLI version that the customer specified.

This PR makes that when the Version tag is empty it creates an 0.0.0 version which will be used to pack the package.

Currently how it works is that we read the .nuspec and validate that Version is not null, this validation is used by the nuget spec and nuget pack commands. Removing an error is a breaking change, so I'm not sure if this is the best solution but for me the scenario for an empty Version tag is not common and setting the 0.0.0 version sounds less disruptive. Also, I tried to lag a warning but looks like the logger is not close to the validation close.

Since we run this validation when reading the file, I cannot check if we are using the -Version property easily, the other option for to fix this is to move that validation to another place where we can check it against pack args.

PR Checklist

  • Meaningful title, helpful description and a linked NuGet/Home issue
  • Added tests Waiting for team feedback before adding tests
  • Link to an issue or pull request to update docs if this PR changes settings, environment variables, new feature, etc.

@martinrrm martinrrm requested a review from a team as a code owner October 9, 2024 19:41
Copy link
Member

@nkolev92 nkolev92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few questions.

@@ -86,7 +86,9 @@ private static void ReadMetadataValue(ManifestMetadata manifestMetadata, XElemen
manifestMetadata.Id = value;
break;
case "version":
manifestMetadata.Version = NuGetVersion.Parse(value);
NuGetVersion version;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does pack behave when someone passes an actual empty or unparseable version on the commandline?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do a NuGetVersion.TryParse and if that fails we throw:

Error NU5010: Version string specified for package reference ' ' is invalid.
Error NU5010: Version string specified for package reference 'sdf' is invalid.

Empty string:
Missing option value for: '-Version'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you link where that happens?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manifestMetadata.Version = NuGetVersion.Parse(value);
NuGetVersion version;
NuGetVersion.TryParse(value, out version);
manifestMetadata.Version = version ?? new NuGetVersion(0, 0, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we just allowed it be null instead of a default?
Do we ever need to differentiate between that and a 0,0,0?

Would we detect that no version has been set somewhere different?

Another idea would be, what if we didn't set a version element in the nuspec.

Copy link
Contributor Author

@martinrrm martinrrm Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version is used to create the nupkg name, so in this case an empty version in the nuspec will create the package as package.0.0.0.nupkg so most likely there is a way to detect that no version has been set later in the code.

Another idea would be, what if we didn't set a version element in the nuspec.

I'm not sure what would this cause, for me it looks like version is very important

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what would this cause, for me it looks like version is very important

I was thinking as more of a test case actually. What would happen if there was no version specified in the nuspec when we attempt to pack and just pass it on the CLI.

That feels like the closest analog to this test case.

Version is a mandatory element, so I'm just worried about the potential fall out of setting a default version.

How are token replacements done? Can we parse the CLI version and validate it before the we read the manifest?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking as more of a test case actually. What would happen if there was no version specified in the nuspec when we attempt to pack and just pass it on the CLI.

Currently, if you don't have the version tag it will fail saying that the file is incorrect, if it is empty, it will fail because we try to parse that empty string and fail if the parsing returns null.

Can we parse the CLI version and validate it before the we read the manifest?

We do validate the CLI version before reading the manifest, and after reading the manifest we check if we specified a CLI version and replace it with it.

So I think after this conversation I think the best solution is:

  1. Continue validating the CLI version
  2. Don't immediately break when there is an empty Version in the nuspec
  3. When we would do the "replacement", if CLI version is invalid and there is no version in nuspec break. Otherwise continue as how it is right now. (if CLI version is valid it would override the .nuspec version)

Copy link
Member

@nkolev92 nkolev92 Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking, what if we validate once?
Can we inject the CLI version into the nuspec before reading the manifest, or is that not possible?

My fear with not validating the manifest is that we're just introducing an unusual behavior that may lead to missing out a mandatory metadata.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reply, I think I was able to address this concern, I'm validating the CLI version and updating the manifest version if it's a valid version.

Copy link
Contributor

@donnie-msft donnie-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks fine to me, but since I don't know whether this is better, so just commenting for now.

@martinrrm martinrrm force-pushed the dev-martinrrm-empty-version-nuspec branch 3 times, most recently from 9c783f0 to 67ed60d Compare October 21, 2024 18:26
Copy link
Contributor

@donnie-msft donnie-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing my earlier feedback. The implementation looks good to me but some of the tests are unclear.

@jeffkl jeffkl requested a review from donnie-msft October 22, 2024 22:40
@@ -143,6 +145,16 @@ public static Manifest ReadFrom(Stream stream, Func<string, string> propertyProv
// Deserialize it
var manifest = ManifestReader.ReadManifest(document);

// Update manifest metadata version if version was provided by the CLI command
if (propertyProvider is not null && propertyProvider.Target.GetType().Name.Equals("PackArgs"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd rather see a public API change, rather than adding a condition based on a delegate's type name, or other uses of reflection.

Before I spend time looking into the code paths too deeply myself, what other approaches have you considered? I was wondering if having an additional Action<Manifest> delegate (or Func<Manifest, Manifest> delegate, so the method signature/public API wouldn't need to change if Manifest eventually becomes immutable) would allow this method to provide extensibility after deserialization and before validation. But looking just a little bit into the code, that would need a bunch of public APIs on PackageBuilder as well, to pass through to here. I still feel like it might be better than checking the delegate's type name though

Did you try not putting this condition, and always overriding the version when propertyProvider has a value for it? What negative side effects are there?

also, the PR description talks about setting the value to 0.0.0, which is no longer happening, so the PR description needs to be updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try not putting this condition, and always overriding the version when propertyProvider has a value for it? What negative side effects are there?
Yes, that was my first implementation but realized that some tests were failing. After doing some debugging, I realized that this function is used in multiple scenarios, the one I'm trying to solve is when the customer is using a nuspec.

The tests that were failing is in the scenario when we are using pack and a .csproj and nuspec. That path calls Manifest.ReadFrom using another propertyProvider which represents the project file (.csproj) not the nuspec, so the information wasn't correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at other approach's but given that this is very specific scenario I wanted to do minimal changes.

@martinrrm martinrrm force-pushed the dev-martinrrm-empty-version-nuspec branch from c2236c1 to 8a02c4a Compare November 4, 2024 19:22
@martinrrm martinrrm merged commit 9d5bde3 into dev Nov 4, 2024
23 checks passed
@martinrrm martinrrm deleted the dev-martinrrm-empty-version-nuspec branch November 4, 2024 23:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Nuget pack doesn't support blank <version> in .nuspec even though version is passed on the command line
4 participants