Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1cbba05
Add env command design
dsplaisted May 31, 2026
88140c0
Move print-env-script to env command in design
dsplaisted May 31, 2026
76aceb7
Add dotnetup-on-PATH orthogonal setting to env design
dsplaisted Jun 11, 2026
443fd6d
Initial implementation by copilot
dsplaisted Jun 1, 2026
80492b4
Refactor from PrintEnvScriptCommand to EnvScriptCommand
dsplaisted Jun 1, 2026
28920f2
Rename strings and throw if trying to unset profile that doesn't exist
dsplaisted Jun 11, 2026
a9523f5
Add tests
dsplaisted Jun 11, 2026
4028290
Separate putting dotnetup on path from dotnet
dsplaisted Jun 16, 2026
879393b
Make env modifications based on actual state, not what was stored in …
dsplaisted Jun 17, 2026
fd43c72
Terminology improvement
dsplaisted Jun 17, 2026
f0d95ea
Update to DotnetAccessMode terminology
dsplaisted Jun 17, 2026
2ce0a39
Simplify path export code
dsplaisted Jun 17, 2026
018cb94
Clean up code
dsplaisted Jun 17, 2026
be903cb
Use true/false instead of on/off or yes/no
dsplaisted Jun 17, 2026
b2d1492
Detect whether env state changes have taken effect, and show message …
dsplaisted Jun 17, 2026
2f4b463
Use current config when `env script` is called without parameters
dsplaisted Jun 17, 2026
6450069
Remove unused using
dsplaisted Jun 17, 2026
34d6b6e
Fix Linux test issue
dsplaisted Jun 17, 2026
eddece0
Apply copilot code review feedback
dsplaisted Jun 18, 2026
4bed5c3
Rename env option from all to full
dsplaisted Jun 19, 2026
ff37b1e
Apply code review feedback
dsplaisted Jun 21, 2026
a5c5866
Merge branch 'release/dnup' into feature/dotnetup-path-mode
nagilson Jun 22, 2026
a0b2b9a
Simplify design doc per code review feedback
dsplaisted Jul 2, 2026
b1d2a47
Apply code review feedback
dsplaisted Jul 7, 2026
1bb89b5
Merge remote-tracking branch 'origin/release/dnup' into feature/dotne…
dsplaisted Jul 7, 2026
1ddcecc
Post-merge fixup
dsplaisted Jul 7, 2026
ac7de84
Rename access mode full -> everywhere
dsplaisted Jul 7, 2026
12b75c5
Apply code review feedback
dsplaisted Jul 13, 2026
1edabc0
Fix style issues
dsplaisted Jul 13, 2026
aff4911
Merge remote-tracking branch 'origin/release/dnup' into feature/dotne…
dsplaisted Jul 13, 2026
a2590ad
Fix issue looking up current dotnet install
dsplaisted Jul 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
529 changes: 529 additions & 0 deletions documentation/general/dotnetup/designs/dotnetup-env.md

Large diffs are not rendered by default.

This file was deleted.

This file was deleted.

39 changes: 39 additions & 0 deletions src/Installer/dotnetup.Library/Commands/Env/EnvClearCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using Microsoft.DotNet.Tools.Bootstrapper.Shell;

namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Env;

/// <summary>
/// Fully removes everything dotnetup wrote into the environment: equivalent to
/// <c>env set none --dotnetup-on-path false</c>. Removes the managed profile block and the
/// dotnetup PATH entry, leaving no dotnet access and no dotnetup-on-PATH. The closest thing
/// to an "env uninstall" since dotnetup has no uninstall command.
/// </summary>
internal class EnvClearCommand : CommandBase
{
private readonly IDotnetEnvironmentManager _dotnetEnvironment;
private readonly IEnvironmentStateInspector _inspector;
private readonly IEnvShellProvider? _shellProvider;

public EnvClearCommand(ParseResult result, IDotnetEnvironmentManager? dotnetEnvironment = null, IEnvironmentStateInspector? inspector = null) : base(result)
{
_dotnetEnvironment = dotnetEnvironment ?? new DotnetEnvironmentManager();
_inspector = inspector ?? new EnvironmentStateInspector(_dotnetEnvironment);
_shellProvider = result.GetValue(CommonOptions.ShellOption);
}

protected override string GetCommandName() => "env clear";

protected override void ExecuteCore()
{
EnvSettingsWriter.ApplyAndPersist(
DotnetAccessMode.None,
targetDotnetupOnPath: false,
Comment thread
dsplaisted marked this conversation as resolved.
_dotnetEnvironment,
_shellProvider,
_inspector);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;

namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Env;

internal static class EnvClearCommandParser
{
public static Command ConstructCommand()
{
Command command = new("clear", "Remove all dotnetup environment wiring (equivalent to 'env set none --dotnetup-on-path false').");
command.Options.Add(CommonOptions.ShellOption);
command.SetAction(parseResult => new EnvClearCommand(parseResult).Execute());
return command;
}
}
26 changes: 26 additions & 0 deletions src/Installer/dotnetup.Library/Commands/Env/EnvCommandParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;

namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Env;

internal static class EnvCommandParser
{
private static readonly Command s_envCommand = ConstructCommand();

public static Command GetCommand() => s_envCommand;

private static Command ConstructCommand()
{
Command command = new("env", "Manage the dotnetup environment configuration (PATH and DOTNET_ROOT wiring).");

command.Subcommands.Add(EnvSetCommandParser.ConstructCommand());
command.Subcommands.Add(EnvClearCommandParser.ConstructCommand());
command.Subcommands.Add(EnvShowCommandParser.ConstructCommand());
command.Subcommands.Add(EnvScriptCommandParser.ConstructCommand(name: "script"));

return command;
}
}

68 changes: 68 additions & 0 deletions src/Installer/dotnetup.Library/Commands/Env/EnvDriftAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;

namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Env;

/// <summary>
/// Pure comparison of the configured env settings against an observed environment snapshot,
Comment thread
dsplaisted marked this conversation as resolved.
Outdated
/// producing human-readable drift descriptions. Kept free of any environment reads (the
/// <see cref="EnvironmentStateInspector"/> does those) so it can be unit-tested without touching
/// the registry or shell profiles. Used by <see cref="EnvShowCommand"/>.
/// </summary>
internal static class EnvDriftAnalyzer
{
public static IReadOnlyList<string> Compare(DotnetupConfigData config, ObservedEnvironmentState observed)
{
ArgumentNullException.ThrowIfNull(config);
Comment thread
dsplaisted marked this conversation as resolved.
Outdated
ArgumentNullException.ThrowIfNull(observed);

var drift = new List<string>();

bool expectsProfileDotnet = config.AccessMode is DotnetAccessMode.Shell or DotnetAccessMode.All;
bool expectsProfileBlock = expectsProfileDotnet || config.DotnetupOnPath;
bool expectsDotnetEnvVars = config.AccessMode == DotnetAccessMode.All;

// Profile-block presence: only assert when the profile state is known.
if (observed.ProfileBlockPresent is bool profileBlockPresent)
Comment thread
dsplaisted marked this conversation as resolved.
Outdated
{
if (expectsProfileBlock && !profileBlockPresent)
{
drift.Add("Shell profile is missing the dotnetup managed block.");
Comment thread
dsplaisted marked this conversation as resolved.
Outdated
}
else if (!expectsProfileBlock && profileBlockPresent)
{
drift.Add("Shell profile contains a dotnetup managed block but neither dotnet access nor dotnetup-on-PATH is configured.");
}
}

if (OperatingSystem.IsWindows())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I worry that this adds another maintenance point about only supporting 'full' path on windows - which I think will change shortly since we need to support mac gui apps and linux gui apps not launched from the shell. I would suggest changing this pattern.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This code could be made more future-proof, but I'm pretty sure it works as is and changing it would add some risk of breaking it. Plus, I don't know what "everywhere" mode on non-Windows will look like, and we might still end up with OS-specific code in the end. So I'd prefer to leave it as is for now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Totally understandable, let's keep this as is.

{
if (expectsDotnetEnvVars && !observed.DotnetUserEnvVarsComplete)
{
drift.Add("Windows user PATH / DOTNET_ROOT / system PATH do not match 'all' mode expectations.");
}
else if (!expectsDotnetEnvVars && observed.DotnetUserEnvVarsPresent)
{
drift.Add(string.Format(
CultureInfo.InvariantCulture,
"Windows user PATH / DOTNET_ROOT still has 'all'-mode wiring (expected dotnet access: '{0}').",
config.AccessMode.ToString().ToLowerInvariant()));
}

// The user-scope PATH is authoritative for dotnetup-on-PATH on Windows (the profile
Comment thread
dsplaisted marked this conversation as resolved.
Outdated
// block copy is just a convenience).
if (config.DotnetupOnPath && !observed.DotnetupOnUserPath)
Comment thread
dsplaisted marked this conversation as resolved.
{
drift.Add("dotnetup is configured to be on PATH but is missing from the user PATH.");
}
else if (!config.DotnetupOnPath && observed.DotnetupOnUserPath)
{
drift.Add("dotnetup is on the user PATH but is configured to be off.");
}
}

return drift;
}
}
Loading
Loading