Skip to content

Expose host-side environment injection for ProjectCollection and nested BuildManager execution #15216

Description

@baronfel

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.

Activity

  1. added theissue type on Oct 6, 2026
  2. JanProvaznik commented on Oct 8, 2026

    @JanProvaznik
    Member

    grooming notes:

    Push the static-graph restore multithreading prototype and design summary to a branch, then ask Jeff to review the proposed environment-variable reader shim and identify design issues.

    architectural direction was plausible but that API-driven static graph restore evaluations were not among highest-priority use cases. They would prefer broader support for static graph restore and shared caches, but the exploratory work was not identified as an immediate commitment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions