Summary
Please expose a supported host-side environment abstraction or snapshot-injection API for project evaluation and nested builds. This is needed when a native multithreaded task invokes MSBuild APIs using its injected TaskEnvironment rather than the host process environment.
Scenario
NuGet static graph restore currently runs in a separate process. While migrating it to a native [MSBuildMultiThreadableTask] implementing IMultiThreadableTask, we can resolve paths and snapshot variables through TaskEnvironment, but cannot pass that snapshot through the public MSBuild evaluation/build APIs.
The runner creates a private ProjectCollection, constructs a ProjectGraph with ProjectInstance.FromFile(path, ProjectOptions), and executes _CollectRestoreInputs with a private BuildManager. SaveOperatingEnvironment=false and MultiThreaded=true avoid saving/restoring process state, but do not make evaluation or nested tasks use the caller's environment snapshot.
Concrete reproduction
The host creates a task environment containing:
TaskEnvironment.CreateWithProjectDirectoryAndEnvironment(projectDirectory,
new Dictionary<string, string>
{
["NATIVE_GRAPH_PACKAGE_ID"] = "Task.Local.Package"
});
The project contains:
<PropertyGroup>
<PackageId>$(NATIVE_GRAPH_PACKAGE_ID)</PackageId>
</PropertyGroup>
The variable is absent from the host process environment. The native task can read Task.Local.Package from its task environment, but the ProjectInstance created through the private collection evaluates PackageId as empty. NuGet subsequently falls back to the project filename. The same issue affects environment-dependent imports, conditions, and package references.
This was reproduced with Microsoft.Build 18.6.3. Inspection of current main also shows ProjectCollection obtaining environment properties through process reads, internal environment-property hooks, and read-only public BuildParameters.EnvironmentProperties/BuildProcessEnvironment accessors.
Requested capability
Provide a public abstraction or immutable snapshot that callers can inject into ProjectCollection/ProjectOptions and BuildParameters, covering:
- Environment properties used during evaluation, preserving normal environment-property precedence (not converting them to global properties).
- The environment inherited by nested tasks and task hosts/processes.
- Task-local modifications and explicit absence of variables without falling back to the host process.
- Concurrent independent collections/build managers without process-wide environment mutation.
Using reflection against internal hooks or temporarily setting process environment variables would not be safe for multithreaded task execution. Passing environment variables as global properties also changes evaluation precedence and is not equivalent.
Expected behavior
Two concurrent native callers with different environment snapshots should evaluate and build against their own values, without changing or reading the other caller's state. Existing callers that do not inject an environment should retain current behavior.
Summary
Please expose a supported host-side environment abstraction or snapshot-injection API for project evaluation and nested builds. This is needed when a native multithreaded task invokes MSBuild APIs using its injected
TaskEnvironmentrather than the host process environment.Scenario
NuGet static graph restore currently runs in a separate process. While migrating it to a native
[MSBuildMultiThreadableTask]implementingIMultiThreadableTask, we can resolve paths and snapshot variables throughTaskEnvironment, but cannot pass that snapshot through the public MSBuild evaluation/build APIs.The runner creates a private
ProjectCollection, constructs aProjectGraphwithProjectInstance.FromFile(path, ProjectOptions), and executes_CollectRestoreInputswith a privateBuildManager.SaveOperatingEnvironment=falseandMultiThreaded=trueavoid saving/restoring process state, but do not make evaluation or nested tasks use the caller's environment snapshot.Concrete reproduction
The host creates a task environment containing:
The project contains:
The variable is absent from the host process environment. The native task can read
Task.Local.Packagefrom its task environment, but theProjectInstancecreated through the private collection evaluatesPackageIdas empty. NuGet subsequently falls back to the project filename. The same issue affects environment-dependent imports, conditions, and package references.This was reproduced with Microsoft.Build 18.6.3. Inspection of current main also shows
ProjectCollectionobtaining environment properties through process reads, internal environment-property hooks, and read-only publicBuildParameters.EnvironmentProperties/BuildProcessEnvironmentaccessors.Requested capability
Provide a public abstraction or immutable snapshot that callers can inject into
ProjectCollection/ProjectOptionsandBuildParameters, covering:Using reflection against internal hooks or temporarily setting process environment variables would not be safe for multithreaded task execution. Passing environment variables as global properties also changes evaluation precedence and is not equivalent.
Expected behavior
Two concurrent native callers with different environment snapshots should evaluate and build against their own values, without changing or reading the other caller's state. Existing callers that do not inject an environment should retain current behavior.