6

I have a solution with 10 projects, I used to distribute this as an SDK with an installer back in the day.

To change that in VSTS I added the steps to create a package per project, every time I make a change, a build is triggered and a PS script change the version of the nuspec files, in the end the packages are pushed to a feed, if I made a change to just one project the build pushes a new version of every packages even though there are no changes in all of them.

Someone told me this was wrong, I should not push a new version if there was no change in the package and every package has to be created separately.

That makes sense, but in my case, I have to create 9 more builds in VSTS, replace the project references for nuget dependencies, and, if I ever need to change two projects I have to modify one, commit the change, then open the second project update the nuget reference and make the change, which seems like a lot of extra work.

So my question is, is there a best practice or recommendation about the flow in creating a group of nuget packages?

1
  • 1
    VSTS has directory filtering. You can keep your repository as a whole, but you might need those 9 new builds.
    – RubberDuck
    Sep 6, 2017 at 16:19

2 Answers 2

6

I'm using Azure DevOps (formerly VSTS), and while this question is old, it may have value for others.

My understanding of best practice is:

  • Use semantic versioning
  • Separate package groups into distinct repositories where possible
  • Don't update a package version if the package isn't changed
    • This is tricky with version data coming from a CI build and n package projects in a .sln
  • Organize NuGet feeds such that any feed can be used by any number of projects, but any one project uses only one feed (ie. employ upstream feeds)

Only Version Changed Packages

There are legitimate situations for many projects in a single solution

This has almost always been true.

Avoid the build collection balloon

For those situations, you CAN do as you have stated in your question and create a build for each .csproj file in the .sln and trigger on the path containing that .csproj, but I might not recommend it.

Though I like the idea of discrete identification of why a build exists, I don't like the idea of creating a new build pipeline any time a developer adds a project to the .sln. As I'm wearing 2 hats - one as the primary DevOps Engineer and another as a Sr. Developer - I don't like setting myself up for loads of new build pipeline requests from my team if I can help it.

Powershell to the rescue

It's a common (and accurate) saying:

Just because you CAN do something in Powershell doesn't mean you should.

However, I don't believe I'm abusing this tool here.

I'm opting to let the build chew on the .sln file and build everything, and even create a new package version for all the projects based on the build number.

But... When it comes time to move the packages into the Build Artifact Staging area, I want Powershell to take the generic Copy Files task to school and only copy packages produced by a .csproj that has actually been changed.

How?

By iterating over the commits linked to the build and first using git cat-file -p $commit to make sure that the commit isn't a Merge (non-merge commits only have 1 parent listed) and then use git diff-tree --no-commit-id --name-only -r $commit to get the files changed for those commits. With that data in hand I can index the direct sub-directories of my build trigger directory (read: the directory containing the .sln), which should be the project directories, and copy the packages where the package name contains the project directory name.

Because our convention for where projects live under a solution is well developed, I can make certain assumptions about what should be done based on the output of the git commands.

Why?

Doing this "Dynamic Artifact Composition" allows the .sln to grow or shrink naturally and no additional changes to the CI are required. New projects are detected and their packages included, while removed projects are simply no longer available to produce a package for the script to copy.

This approach also gives a direct relationship between the artifact produced, which only includes Some.Lib.1.0.1907.302.nupkg, with the commits shown in the build summary, which show changes to src/sln/Some.Lib/logic/ChangedClass.cs for example.

Pump the breaks

I mentioned our convention. You need to make sure you can actually do this based on what your project and solution directory structure looks like. Solution authoring allows one to have a lot of freedom for adding projects that live outside the solution directory. While this doesn't remove the option to do it this way, you need to make sure your script is flexible according to your environment.

2
  • Thank you for your thoughtful response. It certainly helped me, even all these years later!
    – Treker
    Aug 26, 2022 at 3:17
  • 1
    I should add to this response that Project References within the solution should also be included in the artifact. Example: if A references B and A changed, my answer there will version both A and B. Even though B didn't change, it should be included because of A's dependency upon it. NuGet + dotnet transform project references into package dependencies, so the new version of A will automatically be looking for the new version of B. If you fail to publish it, you're in for some issues.
    – Josh Gust
    Sep 2, 2022 at 17:22
2

I am not an expert on NuGet or VSTS so can only offer general advice.

As a general pattern, not just NuGet, for interlinked projects the pattern consists of:

  • structuring your project to have common code clearly separated from application or project code.
  • Minimising or removing all inter-dependencies between projects - ideally all of the projects should be self contained and externally dependent only on common code.
  • Good build tools that only re-build the projects that are impacted and can automate distribution of the resulting builds.
  • A clear and stable API for all inter-project communications, (which includes file structures and anything else that is exchanged between projects).
  • Including versioning in any exchanges and attempting to maintain backwards compatibility where possible so if projects get out of step they either still work or give a clear and helpful error.
  • Using semantic versioning or compatibility masks to flag when updates are likely to need to be more widely performed.
  • Lots of testing, preferably automated testing, so when you are considering pushing a new version of one package all of the other packages are tested to see if they are still compatibly with that revised version and if changes impact other projects it is flagged that they need to get updated at the same time.

A general rule of thumb is that if your change does not impact anything that is likely to be consumed by one of your other projects then it is unlikely to require changes to the other projects.

You can also look at:

  • Automated updating of the interlinked versions of packages
  • Are the updated references predictable.
  • Using a more sane or less MS project management system than VSTS that handles interlinked projects better.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.