Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.ContractsLight;
using Newtonsoft.Json;
Expand Down Expand Up @@ -48,39 +49,45 @@ public readonly struct ProjectGraphWithPredictionsResult<TPathType>
/// <nodoc/>
public bool Succeeded { get; }

/// <summary>
/// A list of the names of environment variables that could affect the build.
/// </summary>
public IEnumerable<string> EnvironmentVariablesAffectingBuild { get; }

/// <nodoc/>
public static ProjectGraphWithPredictionsResult<TPathType> CreateSuccessfulGraph(ProjectGraphWithPredictions<TPathType> projectGraphWithPredictions, IReadOnlyDictionary<string, TPathType> assemblyPathsToLoad, TPathType pathToMsBuild)
public static ProjectGraphWithPredictionsResult<TPathType> CreateSuccessfulGraph(ProjectGraphWithPredictions<TPathType> projectGraphWithPredictions, IReadOnlyDictionary<string, TPathType> assemblyPathsToLoad, TPathType pathToMsBuild, IEnumerable<string> environmentVariablesAffectingBuild)
{
Contract.Requires(projectGraphWithPredictions != null);
Contract.Requires(assemblyPathsToLoad != null);
return new ProjectGraphWithPredictionsResult<TPathType>(projectGraphWithPredictions, failure: default, msBuildAssemblyPaths: assemblyPathsToLoad, pathToMsBuild: pathToMsBuild, pathToDotNetExe: default(TPathType), succeeded: true);
return new ProjectGraphWithPredictionsResult<TPathType>(projectGraphWithPredictions, failure: default, msBuildAssemblyPaths: assemblyPathsToLoad, pathToMsBuild: pathToMsBuild, pathToDotNetExe: default(TPathType), succeeded: true, environmentVariablesAffectingBuild);
}

/// <nodoc/>
public static ProjectGraphWithPredictionsResult<TPathType> CreateFailure(GraphConstructionError failure, IReadOnlyDictionary<string, TPathType> assemblyPathsToLoad, TPathType pathToMsBuild)
{
Contract.Requires(failure != null);
Contract.Requires(assemblyPathsToLoad != null);
return new ProjectGraphWithPredictionsResult<TPathType>(default, failure, assemblyPathsToLoad, pathToMsBuild, pathToDotNetExe: default(TPathType), succeeded: false);
return new ProjectGraphWithPredictionsResult<TPathType>(default, failure, assemblyPathsToLoad, pathToMsBuild, pathToDotNetExe: default(TPathType), succeeded: false, environmentVariablesAffectingBuild: null);
}

/// <summary>
/// Returns a new instance of this with a specific path to dotnet.exe
/// </summary>
public ProjectGraphWithPredictionsResult<TPathType> WithPathToDotNetExe(TPathType pathToDotNetExe)
{
return new ProjectGraphWithPredictionsResult<TPathType>(Result, Failure, MsBuildAssemblyPaths, PathToMsBuild, pathToDotNetExe, Succeeded);
return new ProjectGraphWithPredictionsResult<TPathType>(Result, Failure, MsBuildAssemblyPaths, PathToMsBuild, pathToDotNetExe, Succeeded, EnvironmentVariablesAffectingBuild);
}

[JsonConstructor]
private ProjectGraphWithPredictionsResult(ProjectGraphWithPredictions<TPathType> result, GraphConstructionError failure, IReadOnlyDictionary<string, TPathType> msBuildAssemblyPaths, TPathType pathToMsBuild, TPathType pathToDotNetExe, bool succeeded)
private ProjectGraphWithPredictionsResult(ProjectGraphWithPredictions<TPathType> result, GraphConstructionError failure, IReadOnlyDictionary<string, TPathType> msBuildAssemblyPaths, TPathType pathToMsBuild, TPathType pathToDotNetExe, bool succeeded, IEnumerable<string> environmentVariablesAffectingBuild)
{
Result = result;
Failure = failure;
Succeeded = succeeded;
PathToMsBuild = pathToMsBuild;
PathToDotNetExe = pathToDotNetExe;
MsBuildAssemblyPaths = msBuildAssemblyPaths;
EnvironmentVariablesAffectingBuild = environmentVariablesAffectingBuild;
}
}
}
14 changes: 12 additions & 2 deletions Public/Src/FrontEnd/MsBuild/MsBuildResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,25 @@ private Task<bool> EvaluateAllFilesAsync(IReadOnlySet<AbsolutePath> evaluationGo
.Where(project => ProjectMatchesQualifier(project, qualifier))
.ToReadOnlySet();

IEnumerable<KeyValuePair<string, string>> pipEnvironment = m_msBuildWorkspaceResolver.UserDefinedEnvironment;

// If a unique environment wasn't specified and the project graph determined the environment variables affecting the build,
// we're going to give the subset of those environment variables to the Pip.
if (m_msBuildResolverSettings.Environment == null && result.EnvironmentVariablesAffectingBuild != null)
{
var graphedEnvVars = result.EnvironmentVariablesAffectingBuild.ToHashSet();
pipEnvironment = m_msBuildWorkspaceResolver.UserDefinedEnvironment.Where(kvp => graphedEnvVars.Contains(kvp.Key));
}

var graphConstructor = new PipGraphConstructor(
m_context,
m_host,
result.ModuleDefinition,
m_msBuildResolverSettings,
result.MsBuildLocation,
result.DotNetExeLocation,
m_frontEndName,
m_msBuildWorkspaceResolver.UserDefinedEnvironment,
m_frontEndName,
pipEnvironment,
m_msBuildWorkspaceResolver.UserDefinedPassthroughVariables);

return graphConstructor.TrySchedulePipsForFilesAsync(filteredBuildFiles, qualifierId);
Expand Down
28 changes: 20 additions & 8 deletions Public/Src/FrontEnd/MsBuild/MsBuildWorkspaceResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ private async Task<Possible<ProjectGraphResult>> TryComputeBuildGraphAsync(IEnum
allowedModuleDependencies: null, // no module policies
cyclicalFriendModules: null); // no whitelist of cycles

return new ProjectGraphResult(projectGraph, moduleDefinition, projectGraphResult.PathToMsBuild, projectGraphResult.PathToDotNetExe);
return new ProjectGraphResult(projectGraph, moduleDefinition, projectGraphResult.PathToMsBuild, projectGraphResult.PathToDotNetExe, projectGraphResult.EnvironmentVariablesAffectingBuild);
}

private void DeleteGraphBuilderRelatedFiles(AbsolutePath outputFile, AbsolutePath responseFile)
Expand Down Expand Up @@ -538,8 +538,6 @@ private async Task<Possible<ProjectGraphWithPredictionsResult<AbsolutePath>>> Co
standardError);
}

TrackFilesAndEnvironment(result.AllUnexpectedFileAccesses, outputFile.GetParent(m_context.PathTable));

var serializer = JsonSerializer.Create(ProjectGraphSerializationSettings.Settings);
serializer.Converters.Add(new AbsolutePathJsonConverter(m_context.PathTable));
serializer.Converters.Add(new ValidAbsolutePathEnumerationJsonConverter());
Expand All @@ -549,6 +547,8 @@ private async Task<Possible<ProjectGraphWithPredictionsResult<AbsolutePath>>> Co
{
var projectGraphWithPredictionsResult = serializer.Deserialize<ProjectGraphWithPredictionsResult<AbsolutePath>>(reader);

TrackFilesAndEnvironment(result.AllUnexpectedFileAccesses, outputFile.GetParent(m_context.PathTable), projectGraphWithPredictionsResult.EnvironmentVariablesAffectingBuild);

// A successfully constructed graph should always have a valid path to MsBuild
Contract.Assert(!projectGraphWithPredictionsResult.Succeeded || projectGraphWithPredictionsResult.PathToMsBuild.IsValid);
// A successfully constructed graph should always have at least one project node
Expand Down Expand Up @@ -586,15 +586,27 @@ private bool TryFindDotNetExe(IEnumerable<AbsolutePath> dotnetSearchLocations, o
return false;
}

private void TrackFilesAndEnvironment(ISet<ReportedFileAccess> fileAccesses, AbsolutePath frontEndFolder)
private void TrackFilesAndEnvironment(ISet<ReportedFileAccess> fileAccesses, AbsolutePath frontEndFolder, IEnumerable<string> environmentVariablesAffectingBuild)
{
// Register all build parameters passed to the graph construction process
// Observe passthrough variables are explicitly skipped: we don't want the engine to track them
// TODO: we actually need the build parameters *used* by the graph construction process, but for now this is a compromise to keep
// graph caching sound. We need to modify this when MsBuild static graph API starts providing used env vars.
foreach (string key in m_userDefinedEnvironment.Keys)

// If a specific environment wasn't passed in AND if the graph's env vars parameter is NOT null
// (meaning we successfully retrieved the list of environment variables that affect
// the build during evaluation), then tell the engine to track them.
if (m_resolverSettings.Environment == null && environmentVariablesAffectingBuild != null)
{
foreach (string environmentVariable in environmentVariablesAffectingBuild)
{
m_host.Engine.TryGetBuildParameter(environmentVariable, MsBuildFrontEnd.Name, out _);
}
}
else
{
m_host.Engine.TryGetBuildParameter(key, MsBuildFrontEnd.Name, out _);
foreach (string key in m_userDefinedEnvironment.Keys)
{
m_host.Engine.TryGetBuildParameter(key, MsBuildFrontEnd.Name, out _);
}
}

FrontEndUtilities.TrackToolFileAccesses(m_host.Engine, m_context, MsBuildFrontEnd.Name, fileAccesses, frontEndFolder);
Expand Down
7 changes: 6 additions & 1 deletion Public/Src/FrontEnd/MsBuild/ProjectGraphResult.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Diagnostics.ContractsLight;
using BuildXL.FrontEnd.MsBuild.Serialization;
using BuildXL.FrontEnd.Workspaces.Core;
Expand All @@ -27,7 +28,10 @@ public readonly struct ProjectGraphResult
public AbsolutePath DotNetExeLocation { get; }

/// <nodoc/>
public ProjectGraphResult(ProjectGraphWithPredictions<AbsolutePath> projectGraphWithPredictions, ModuleDefinition moduleDefinition, AbsolutePath msBuildLocation, AbsolutePath dotnetExeLocation)
public IEnumerable<string> EnvironmentVariablesAffectingBuild { get; }

/// <nodoc/>
public ProjectGraphResult(ProjectGraphWithPredictions<AbsolutePath> projectGraphWithPredictions, ModuleDefinition moduleDefinition, AbsolutePath msBuildLocation, AbsolutePath dotnetExeLocation, IEnumerable<string> environmentVariablesAffectingBuild)
{
Contract.Requires(projectGraphWithPredictions != null);
Contract.Requires(moduleDefinition != null);
Expand All @@ -37,6 +41,7 @@ public ProjectGraphResult(ProjectGraphWithPredictions<AbsolutePath> projectGraph
ModuleDefinition = moduleDefinition;
MsBuildLocation = msBuildLocation;
DotNetExeLocation = dotnetExeLocation;
EnvironmentVariablesAffectingBuild = environmentVariablesAffectingBuild;
}
}
}
23 changes: 21 additions & 2 deletions Public/Src/Tools/Tool.MsBuildGraphBuilder/MsBuildGraphBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Reflection;
using System.Threading.Tasks;
using BuildXL.FrontEnd.MsBuild.Serialization;
using BuildXL.Utilities;
using BuildXL.Utilities.Instrumentation.Common;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
Expand All @@ -24,6 +25,8 @@
using ProjectGraphWithPredictions = BuildXL.FrontEnd.MsBuild.Serialization.ProjectGraphWithPredictions<string>;
using ProjectWithPredictions = BuildXL.FrontEnd.MsBuild.Serialization.ProjectWithPredictions<string>;
using BuildXL.Utilities.Collections;
using Microsoft.Build.Framework;
using ProjectGraphBuilder;

namespace MsBuildGraphBuilderTool
{
Expand Down Expand Up @@ -144,10 +147,12 @@ private static ProjectGraphWithPredictionsResult BuildGraphInternal(
locatedMsBuildPath);
}

var environmentVariablesLogger = new PropertyTrackingLogger();

var projectGraph = new ProjectGraph(
entryPoints,
// The project collection doesn't need any specific global properties, since entry points already contain all the ones that are needed, and the project graph will merge them
new ProjectCollection(),
new ProjectCollection(globalProperties: null, loggers: new ILogger[]{ environmentVariablesLogger }, ToolsetDefinitionLocations.Default),
(projectPath, globalProps, projectCollection) => ProjectInstanceFactory(projectPath, globalProps, projectCollection, projectInstanceToProjectCache));

// This is a defensive check to make sure the assembly loader actually honored the search locations provided by the user. The path of the assembly where ProjectGraph
Expand Down Expand Up @@ -182,7 +187,21 @@ private static ProjectGraphWithPredictionsResult BuildGraphInternal(
locatedMsBuildPath);
}

return ProjectGraphWithPredictionsResult.CreateSuccessfulGraph(projectGraphWithPredictions, assemblyPathsToLoad, locatedMsBuildPath);
// Get the possible environment variable reads from the logger.
// If there was a failure, report the failure message.
var envVarResult = environmentVariablesLogger.PotentialEnvironmentVariableReads;
var reads = envVarResult.Succeeded ? envVarResult.Result : (IReadOnlyCollection<string>)null;

if (!envVarResult.Succeeded)
{
reporter.ReportMessage(envVarResult.Failure.Describe());
}

return ProjectGraphWithPredictionsResult.CreateSuccessfulGraph(
projectGraphWithPredictions,
assemblyPathsToLoad,
locatedMsBuildPath,
reads);
}
catch (InvalidProjectFileException e)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using BuildXL.Utilities;
using BuildXL.Utilities.Collections;
using Microsoft.Build.Framework;

namespace ProjectGraphBuilder
{
/// <summary>
/// Logger that handles property tracking events.
/// </summary>
internal sealed class PropertyTrackingLogger : ILogger
{
private readonly ConcurrentDictionary<string, object> m_variablesRead = new ConcurrentDictionary<string, object>(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, object> m_sdkResolversNotTrackingEnvVars = new ConcurrentDictionary<string, object>();

public PropertyTrackingLogger()
{
// This environment variable enable property tracking in MsBuild.
// Without it, no events would be logged or received.
// A value of "12" tells MsBuild to log events "Environment Variable Read" and "Uninitialized Property Read".
Environment.SetEnvironmentVariable("MsBuildLogPropertyTracking", "12");
}

public void Initialize(IEventSource eventSource)
{
eventSource.AnyEventRaised += (sender, args) =>
{
if (args is EnvironmentVariableReadEventArgs evrArgs)
{
m_variablesRead[evrArgs.EnvironmentVariableName] = null;
}
else if (args is UninitializedPropertyReadEventArgs upArgs)
{
m_variablesRead[upArgs.PropertyName] = null;
}
// This is for future work being done.
//else if (args is SdkResolverDoesNotTrackEnvironmentVariablesEventArgs trackArgs)
//{
// m_sdkResolversNotTrackingEnvVars[trackArgs.SdkResolverName] = null;
//}
};
}

public void Shutdown()
{
}

public LoggerVerbosity Verbosity { get; set; }

public string Parameters { get; set; }

/// <summary>
/// The list of environment variables that would be read by the build system if present.
/// </summary>
public Possible<IReadOnlyCollection<string>> PotentialEnvironmentVariableReads
{
get
{
if (m_sdkResolversNotTrackingEnvVars.Count == 0)
{
return new Possible<IReadOnlyCollection<string>>(m_variablesRead.Keys.ToArray());
}

string errorMessage = null;

lock (m_sdkResolversNotTrackingEnvVars)
{
errorMessage = $"Unreliable environment variable tracking due to SdkResolvers not participating in tracking. Offenders: {string.Join(",", m_sdkResolversNotTrackingEnvVars)}";
}

return new Failure<string>(errorMessage);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace MsBuildGraphBuilder {
importFrom("BuildXL.FrontEnd").MsBuild.Serialization.dll,
importFrom("BuildXL.Utilities").Collections.dll,
importFrom("BuildXL.Utilities").Native.dll,
importFrom("BuildXL.Utilities").dll,
importFrom("BuildXL.Utilities.Instrumentation").Common.dll,
...addIf(BuildXLSdk.isFullFramework, importFrom("System.Collections.Immutable").pkg),
...addIf(BuildXLSdk.isFullFramework, importFrom("System.Threading.Tasks.Dataflow").pkg),
Expand Down
11 changes: 11 additions & 0 deletions Public/Src/Tools/UnitTests/MsBuildGraphBuilder/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.DataFlow" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.9.0.0" newVersion="4.5.24.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Loading