sdk: dotnet tool install should return exit code 0 if tool is already installed
I’m trying to write a script that will install global tools if necessary. I would like to just run “dotnet tool install”, but this returns exit code 0 if the tool is already present, so I have to first execute dotnet tool list and grep the output.
Expected behavior
Calling dotnet tool install should no-op AND exit code 0 if the tool is already installed
Actual behavior
Running dotnet tool install twice fails scripts because the second time it runs it will exit code 0
PS> & dotnet tool install --tool-path "$(pwd)/.tools" sleet --version 2.3.25 --add-source https://api.nuget.org/v3/index.json
You can invoke the tool using the following command: sleet
Tool 'sleet' (version '2.3.25') was successfully installed.
PS> $lastexitcode
0
PS> & dotnet tool install --tool-path "$(pwd)/.tools" sleet --version 2.3.25 --add-source https://api.nuget.org/v3/index.json
Tool 'sleet' is already installed.
PS> $lastexitcode
1
The result of this is that I have write more complicate code like this:
if (& $dotnet tool list --tool-path "$PSScriptRoot/.tools" | Select-String "sleet") {
Write-Host -f Yellow 'Skipping install of sleet. It''s already installed'
}
else {
Invoke-Block { & $dotnet tool install --tool-path "$PSScriptRoot/.tools" sleet --version 2.3.25 --add-source https://api.nuget.org/v3/index.json }
}
Environment data
dotnet --info output:
.NET Core SDK (reflecting any global.json):
Version: 2.1.300
Commit: adab45bf0c
Runtime Environment:
OS Name: Windows
OS Version: 10.0.17134
OS Platform: Windows
RID: win10-x64
Base Path: C:\dev\aspnet\Universe20\.dotnet\sdk\2.1.300\
Host (useful for support):
Version: 2.1.0
Commit: caa7b7e2ba
.NET Core SDKs installed:
2.1.300 [C:\dev\aspnet\Universe20\.dotnet\sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.1.0 [C:\dev\aspnet\Universe20\.dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.0 [C:\dev\aspnet\Universe20\.dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.0 [C:\dev\aspnet\Universe20\.dotnet\shared\Microsoft.NETCore.App]
About this issue
- Original URL
- State: open
- Created 6 years ago
- Reactions: 74
- Comments: 42 (6 by maintainers)
Commits related to this issue
- force dotnet upgrade as opposed to install to fix error https://github.com/dotnet/sdk/issues/9500 — committed to foxmoat/mslearn-tailspin-spacegame-web by foxmoat 4 years ago
- nvim: use `tool update` instead of `tool install` dotnet is funny. `tool install` fails if the tool is already installed, and the upstream issue (dotnet/sdk#9500) recommends using `update` instead of... — committed to fsouza/dotfiles by fsouza 3 years ago
npm installreturns 1 when it can’t change to the specified version.npm installreturns 0 when the version is already installed.npm installwithout a version specified will update to the latest version when the package is already installed.This matches my expectations as a user.
@KathleenDollard It’s not a “bad” choice. But IMO, it’s less than ideal. When scripting something it would be great to simply use
dotnet installas an idempotent command meaning that it will implicitly “install” if not present, or return 0 if it’s already installed. It just makes life easier, and seems to be an established convention.Another side of the coin will be “I don’t know if it will trigger asset download if I run this command”. I slightly prefer current approach. But note, both approach will cause another side unhappy.
This is overly strict and IMO a bad default. The error code currently issued (exit code 1) is the same error code issued for legitimate errors, so I can’t just swallowed the error and move on.
You’ve brought up a good point though – what to do about versions. IMO it would be good to look at what other tools do, e.g.
From my experience with these, running
install(without a version) will return exit code 0 and will not attempt to upgrade if the tool is already installed. Runninginstallwith a version will attempt to run and upgrade (or downgrade) to bring the installed tool into alignment with the value of--versioncc @KathleenDollard @richlander
It’s so frustrating not to get the common convention of a 0 exit when the tool installs or is already present.
@gravufo The
dotnet tool updatecommand “uninstalls and reinstalls a tool, effectively updating it.” That is quite a joke and definitely not what an idempotentinstallcommand should do, so even that command can’t serve as a proper substitute. The reality is thatdotnet tool installshould be a graceful no-op if the tool is already installed (as everyone here seems to agree, except for MSFT), anddotnet tool updateshould only update if there is a newer version determined to be available (but that’s a different issue for a different thread).Ran into this problem literally the first hour of trying to set up a CI pipeline for my first .net core 3 project. This behavior is so… disheartening and is bad DX.
The added
upgradeoption is nice I guess, but it still downloads the tool every time making it impossible to utilize pipeline caching.All I want to do is install the tool if it doesn’t exist without having to download any extra binaries. Why does this have to be so complicated?
How many people will have to complain here for Microsoft to stop burying its head in the ground and scream that everything is fine?
Edit: I saw that
dotnet tool updatewas changed to have the kind of behavior we are expecting here. In my opinion, Microsoft still has it completely upside down. How does it make sense for a command calledupdateto install if the tool doesn’t exist whereas theinstallcommand fails if the tool already is installed??This is unintuitive This is also undocumented (https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-tool-install) This is universally rejected by MS customers in this thread This is telling you that we don’t need separate install/update commands This is telling you that in CI/CD world we want UPSERT everywhere and parameters for version targeting etc
I agree with everyone that this is a weird and unexpected behavior. Using
dotnet updateworks and solves the problem but it’s not an obvious thing. It sure isn’t a “pit of success”.I just encountered this error on our CI-machine and had to google for a solution.
I honestly think if a survey was run (focused on ux), it would clearly show that the current behaviour is not preferred. It’s inconsistent with so many other experiences like the ones @natemcmaster mentioned above.
No joy is being sparked with the current behaviour. 😂
As an update, we’re thinking of changing the install behavior so that it will not fail if the tool is already installed. This will mean that
dotnet tool installwill pretty much work the same waydotnet tool updatealready does. Because of that, over time we might deprecatedotnet tool update.We would likely add options to allow the old behavior, for example something like
--fail-if-already-installedand--update-only.@cliffchapmanrbx and others participating in this issue, you should upvote the issue https://github.com/dotnet/sdk/issues/10310 which is similar and is currently open.
Using .NET Tools in a CI pipeline is currently painful and requires a lot of scripting to handle errors, which should be handled by
dotnet tooldirectly.I suggested a couple of different approaches that could help. Adding a flag would improve the UX by a lot:
If changing the
installcommand is breaking and not possible, creating a dedicatedensurecommand could work:Expectations:
More details:
Is there any chance this is going to be re-visited? It is a little ridiculous that
dotnet tool installreturns1when the tool already exists. At the very least, would you consider adding a flag as proposed in #10242? This is inconsistent with the behavior of every other package manager out there, that I know of at least.I will say that I agree with the others though, that the current behavior is not normal behavior and “gracefully failing” (i.e. exit with code 0 if already installed) should be the default behavior. If anything, the current behavior should be “opted” in, if a user wants it. i.e. something like
dotnet tool install sometool --error-if-installedI was just providing an option to satisfy the needs of those that want normal behavior without breaking or surprising any users that have gotten used to or have some reason to prefer the current behavior.
I am personally okay with including a flag to get the behavior that I want but I would prefer the current behavior to be “fixed” to something more normal. (If that was an option on the table) Again, ultimately, I’m okay with whatever is decided is best for the majority.
This broke our makefile, which tries to install 4 tools so we can use them to do various things - lambda, cake, and unit test coverage for an AWS lambda project we have.
If any one of the tools are installed, it will exit with a failure, and not install the rest (that’s the normal behaviour of make).
That’s not expected behaviour to me - I went searching for a way around it, and found this issue.
Since there’s not a flag to change behaviour, to fix it we would either have to parse the exit text (maybe what I’ll choose), or just ignore failures for any reason, and carry on with the rest of the installs (not ideal).
I can’t presume exit code 1 is “success” though, because that’s what it gives for DNS lookup failure, unable to connect to a nuget server, etc too.
Here’s our example Makefile:
Can you explain more what you mean? I’m not sure I understand the concern. It seems clear to me that when calling “dotnet tool install” I may download some assets.
Makes it a pain in ansible
My use case (and I’m sure for many others) is for CI scripts. I should be able to put
dotnet tool install (inserttoolhere)and not worry about it failing on subsequent builds. IMO, it doesn’t feel right to useupdatewhen you’re actually looking to just install a specific version.Since the current behavior is to fail with exit code 1 when a tool is already installed, IMO, the safest solution is to add a flag to disable the “exit with error if tool is installed” behavior for users that opt to use the flag.
I thought either of the suggestions that were in dotnet/cli#11259 were fine (
--slient|-sor--no-errors|-ne) but I’d be okay with something like--skip-installed.I agree!
That is good news @dsplaisted !
Expectations of future behavior that I believe most agree on:
dotnet tool installwithout a--versionspecified will try to update to the latest version when the tool is already installed, return0exit code if latest version already installed or if upgrade is successful,1otherwisedotnet tool installwith a--versionspecified will try to upgrade or downgrade to the specified version when the tool is already installed but with a different version than specified, return0exit code if specified version already installed or if upgrade or downgrade is successful,1otherwiseThat’s brilliant. Except I changed the echo output to “dotnet install is dumb”
Posting here because I agree with above comments should at least have a flag to return 0 if installed
@dsplaisted @riverar Following the principle of deterministic builds, if the
--versionis present it means my process expects that particular version every time it runs, consistently.Thus, I’d argue that should be the other way around e.g. A
--do-not-downgradeoption, which would prevent a downgrade when a higher version is already installed.edit: Also, the name of the option is
--version… If option were called--minimum-version, then sure… I’d agree a downgrade would have to be--forced