question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

.NET 8: MSBuild improvements for containers

See original GitHub issue

.NET 8: MSBuild improvements for containers

I get a lot of my inspiration for SDK improvements from container workflows. Containers constrain the MSBuild environment considerably, mostly due to the docker build context. That makes idiomatic experiences like global config at root less than convenient or performant. Separately, MSBuild is not optimized for an immutable by default build environment, which docker offers as a strength.

Samples

Let’s take a look at one our Docker samples:

RUN dotnet restore -r linux-musl-arm

# copy and publish app and libraries
COPY . .
RUN dotnet publish -c release -o /app -r linux-musl-arm --self-contained false --no-restore

Here’s another one:

RUN dotnet restore "Store.ProductApi/Store.ProductApi.csproj"
COPY . .
WORKDIR "/src/Store.ProductApi"
RUN dotnet build "Store.ProductApi.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Store.ProductApi.csproj" -c Release -o /app/publish

There are two important aspects at play:

  • Repeated arguments
  • Careful use of no-blah commands to ensure no repeated work or bookkeeping in an immutable environment.

Ideally, would be a way to lock a configuration and to tell MSBuild that that we’re in an immutable environment.

Locking in a configuraton

Directory.Build.props enables locking in a configuration. It’s not always convenient to create a file for that purpose. However, that’s the effect that I’m after.

Instead, something like the following would be awesome:

dotnet lock-config -c Release -r linux-x64 --self-contained false
dotnet restore
dotnet publish

That’s an improvement since the configuration is just specified once. However, it doesn’t feel right. I don’t want a new command. I just want to be able to lock-in my configuration with an existing command.

Like:

dotnet restore -c Release -r linux-x64 --self-contained false
dotnet publish

Immutable mode

MSBuild (and possibly NuGet) does significant work to check if the environment has changed since the last run of the command. The time since the last command was run could have been as short as <1s ago! There are reasons why that behavior is a good one, but it’s also very conservative.

Today, we have --no-restore and --no-build commands. That’s a lot of extra ceremony.

Here’s a naive option:

dotnet restore
dotnet build --immutable-mode
dotnet publish --immutable-mode

That’s not much of an improvement on --no-restore and no-build.

How about:

dotnet restore --immutable-mode
dotnet build
dotnet build
dotnet build
dotnet publish

That’s looking much better. MSBuild can manage states for me. In this mode, I would expect that only the first dotnet build would run and the second two would just early-exit. Same with the implicit build within restore. Actually, same with the implicit restore within build and publish.

Pulling it all together

The initial idea with locking in a configuration with restore is likely a breaking change. Let’s avoid that.

We could introduce a new --lock argument on restore. It locks in a configuration until restore is run again. That overloads restore a bit, but its also the most basic command. I think it works.

Our experience now looks like the following

dotnet restore -c Release -r linux-x64 --self-contained false --lock
dotnet publish

It’s reasonable for --immutable to also force --lock. That enables the following experience.

dotnet restore -c Release -r linux-x64 --self-contained false --immutable
dotnet build
dotnet build
dotnet build
dotnet publish

build is only run once, which includes build within publish.

Clearly, I don’t want to run dotnet build multiple times. That’s not the point I’m trying to make.

Really, I want just the following:

dotnet restore -c Release -r linux-x64 --self-contained false --immutable
dotnet publish

I want to ensure that three things:

  • I specify my configuration just once across a set of SDK commands.
  • Restore has all the information it needs to be run just once.
  • All commands run optimally or else they fail.

Closing thoughts

These changes have the potential to make it so much easier to make a high-performance docker build (with MSBuild). I’m certain we can make improvements here.

The examples are centered exclusively on locking with restore. That’s likely not a good idea. We’d want to rationalize that.

These types of experiences always break with dotnet test and (to a lesser degree) dotnet run. We’d need to validate that. Both are reasonable to run within containers (particularly dotnet test.

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:4
  • Comments:20 (20 by maintainers)

github_iconTop GitHub Comments

3reactions
DamianEdwardscommented, Jul 7, 2022

It seems there are certainly similarities. Our .NET default yaml files for GitHub Actions and AzDO Pipelines break out into separate steps for restore, build, test, publish, etc. along with opted out of the implied dependent stages for the later steps (e.g. --no-build on dotnet publish), which means passing the various options to each step is required in order for the stage to work correctly. Once you add a RID you need to ensure it’s passed to every stage, which today is a completely manual thing.

2reactions
rainersigwaldcommented, Jul 7, 2022
  • Put your Dockerfile by your project or solution file and match your build context to that scope. Potentially miss MSBuild files in parent directories of your repo.

I want to be super clear that you don’t need parent directories here. If you use Central Package Version Management, for instance, copying *.csproj is flat wrong. Likewise if you have a Directory.Build.props next to your solution or in any subfolder that influences restore in any way. Basically I think the suggestions in the docs are useful only for trivial codebases.

Read more comments on GitHub >

github_iconTop Results From Across the Web

What's new in .NET 8
.NET 8 includes performance and reliability enhancements of the System.Text.Json source generator that are aimed at making the native AOT ...
Read more >
Microsoft.NET.Build.Containers 7.0.400
NET SDK Containers. This package lets you build container images from your projects with a single command.
Read more >
.NET 7 SDK built-in container improvements ...
It lets us create small and secure containers for our .NET applications easily. We went from a first docker image of 216MB down...
Read more >
Download .NET 8 Preview 4 (Offline Installers)
NET 8. Check out all the latest features and improvements in .NET 8 as they are published all in one place and download...
Read more >
Is ASP.NET on the right path? Microsoft .NET 8 preview ...
NET MAUI, improved all-in-one search that covers code as well as IDE features, brace pair colorization and sticky scroll in the editor. Sticky ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found