-
Notifications
You must be signed in to change notification settings - Fork 1.2k
dotnet run -e FOO=BAR passes @(RuntimeEnvironmentVariable)
#52664
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
Changes from 9 commits
7b6deef
d6264a8
fcc45c3
7fb51d2
07f2dc5
7cd0e6b
b73be5a
0871b65
ffb4b83
3f15105
007dd66
b5972e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.ObjectModel; | ||
| using System.Xml; | ||
| using Microsoft.Build.Execution; | ||
| using Microsoft.DotNet.Cli.Utils; | ||
|
|
||
| namespace Microsoft.DotNet.Cli.Commands.Run; | ||
|
|
||
| /// <summary> | ||
| /// Provides utilities for passing environment variables to MSBuild as items. | ||
| /// Environment variables specified via <c>dotnet run -e NAME=VALUE</c> are passed | ||
| /// as <c><RuntimeEnvironmentVariable Include="NAME" Value="VALUE" /></c> items. | ||
| /// </summary> | ||
| internal static class EnvironmentVariablesToMSBuild | ||
| { | ||
| private const string PropsFileName = "dotnet-run-env.props"; | ||
|
|
||
| /// <summary> | ||
| /// Adds environment variables as MSBuild items to a ProjectInstance. | ||
| /// Use this for in-process MSBuild operations (e.g., DeployToDevice target). | ||
| /// </summary> | ||
| /// <param name="projectInstance">The MSBuild project instance to add items to.</param> | ||
| /// <param name="environmentVariables">The environment variables to add.</param> | ||
| public static void AddAsItems(ProjectInstance projectInstance, IReadOnlyDictionary<string, string> environmentVariables) | ||
| { | ||
| foreach (var (name, value) in environmentVariables) | ||
| { | ||
| projectInstance.AddItem(Constants.RuntimeEnvironmentVariable, name, new Dictionary<string, string> | ||
| { | ||
| ["Value"] = value | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a temporary .props file containing environment variables as MSBuild items. | ||
| /// Use this for out-of-process MSBuild operations where you need to inject items via | ||
| /// <c>CustomBeforeMicrosoftCommonProps</c> property. | ||
| /// </summary> | ||
| /// <param name="projectFilePath">The full path to the project file. If null or empty, returns null.</param> | ||
| /// <param name="environmentVariables">The environment variables to include.</param> | ||
| /// <param name="intermediateOutputPath"> | ||
| /// Optional intermediate output path where the file will be created. | ||
| /// If null or empty, defaults to "obj" subdirectory of the project directory. | ||
| /// </param> | ||
| /// <returns>The full path to the created props file, or null if no environment variables were specified or projectFilePath is null.</returns> | ||
| public static string? CreatePropsFile(string? projectFilePath, IReadOnlyDictionary<string, string> environmentVariables, string? intermediateOutputPath = null) | ||
| { | ||
| if (string.IsNullOrEmpty(projectFilePath) || environmentVariables.Count == 0) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| string projectDirectory = Path.GetDirectoryName(projectFilePath) ?? ""; | ||
|
|
||
jonathanpeppers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Normalize path separators - MSBuild may return paths with backslashes on non-Windows | ||
| string normalized = intermediateOutputPath?.Replace('\\', Path.DirectorySeparatorChar) ?? ""; | ||
| string objDir = string.IsNullOrEmpty(normalized) | ||
| ? Path.Combine(projectDirectory, Constants.ObjDirectoryName) | ||
| : Path.IsPathRooted(normalized) | ||
| ? normalized | ||
| : Path.Combine(projectDirectory, normalized); | ||
| Directory.CreateDirectory(objDir); | ||
|
|
||
| // Ensure we return a full path for MSBuild property usage | ||
| string propsFilePath = Path.GetFullPath(Path.Combine(objDir, PropsFileName)); | ||
| using (var stream = File.Create(propsFilePath)) | ||
| { | ||
| WritePropsFileContent(stream, environmentVariables); | ||
| } | ||
|
|
||
| return propsFilePath; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Deletes the temporary environment variables props file if it exists. | ||
| /// </summary> | ||
| /// <param name="propsFilePath">The path to the props file to delete.</param> | ||
| public static void DeletePropsFile(string? propsFilePath) | ||
| { | ||
| if (propsFilePath is not null && File.Exists(propsFilePath)) | ||
| { | ||
| try | ||
| { | ||
| File.Delete(propsFilePath); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| // Best effort cleanup - don't fail the build if we can't delete the temp file | ||
| Reporter.Verbose.WriteLine($"Failed to delete temporary props file '{propsFilePath}': {ex.Message}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds the props file property to the MSBuild arguments. | ||
| /// This uses <c>CustomBeforeMicrosoftCommonProps</c> to inject the props file early in evaluation. | ||
| /// </summary> | ||
| /// <param name="msbuildArgs">The base MSBuild arguments.</param> | ||
| /// <param name="propsFilePath">The path to the props file (from <see cref="CreatePropsFile"/>).</param> | ||
| /// <returns>The MSBuild arguments with the props file property added, or the original args if propsFilePath is null.</returns> | ||
| public static MSBuildArgs AddPropsFileToArgs(MSBuildArgs msbuildArgs, string? propsFilePath) | ||
| { | ||
| if (propsFilePath is null) | ||
| { | ||
| return msbuildArgs; | ||
| } | ||
|
|
||
| // Add the props file via CustomBeforeMicrosoftCommonProps. | ||
| // This ensures the items are available early in evaluation, similar to how we add items | ||
| // directly to ProjectInstance for in-process target invocations. | ||
| var additionalProperties = new ReadOnlyDictionary<string, string>(new Dictionary<string, string> | ||
| { | ||
| [Constants.CustomBeforeMicrosoftCommonProps] = propsFilePath | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little worried about this negatively impacting existing build processes - but I don't really have a way to validate how widely used this is.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A quick search around github shows ~250 instances, but none really appear foundational and some are in unrelated tooling. I'd expect large 1P customers to have some kind of use of this though, because they use all of the weird extensiblity points. I suppose if MSbuild ever makes a first-class way of injecting Items we could move to that.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a spot I didn't like either, here is a GitHub search: We could introduce a new property? I really wanted to avoid creating a temp
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is fine to get in for the first revision and revisit. If we did a new property we'd need to add 'massaging' of that property into Items, and that can also be an error-prone activity due to weird user inputs. |
||
| }); | ||
|
|
||
| return msbuildArgs.CloneWithAdditionalProperties(additionalProperties); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Writes the content of the .props file containing environment variables as items. | ||
| /// </summary> | ||
| private static void WritePropsFileContent(Stream stream, IReadOnlyDictionary<string, string> environmentVariables) | ||
| { | ||
| using var writer = XmlWriter.Create(stream, new XmlWriterSettings | ||
| { | ||
| OmitXmlDeclaration = true, | ||
| Indent = true | ||
| }); | ||
|
|
||
| writer.WriteStartElement("Project"); | ||
| writer.WriteStartElement("ItemGroup"); | ||
|
|
||
| foreach (var (name, value) in environmentVariables) | ||
| { | ||
| writer.WriteStartElement(Constants.RuntimeEnvironmentVariable); | ||
| writer.WriteAttributeString("Include", name); | ||
| writer.WriteAttributeString("Value", value); | ||
| writer.WriteEndElement(); | ||
| } | ||
|
|
||
| writer.WriteEndElement(); // ItemGroup | ||
| writer.WriteEndElement(); // Project | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.