Skip to content

Commit

Permalink
Generate targets in metaproj (dotnet#1497)
Browse files Browse the repository at this point in the history
* Generate targets in metaproj

This allows users to access any target when building a solution.  The target may or may not exist but this will create the corresponding targets in the `.metaproj`.

I had to pass along the target names from the build request all the way down to the solution generator via ReadOnlyCollections.

Closes dotnet#1275

Might be related to dotnet#1494
  • Loading branch information
jeffkl authored Jan 3, 2017
1 parent 468a19f commit bdb4af3
Show file tree
Hide file tree
Showing 6 changed files with 462 additions and 352 deletions.
2 changes: 1 addition & 1 deletion src/XMakeBuildEngine/BackEnd/BuildManager/BuildManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ internal void LoadSolutionIntoConfiguration(BuildRequestConfiguration config, Bu
}

ErrorUtilities.VerifyThrow(FileUtilities.IsSolutionFilename(config.ProjectFullPath), "{0} is not a solution", config.ProjectFullPath);
ProjectInstance[] instances = ProjectInstance.LoadSolutionForBuild(config.ProjectFullPath, config.Properties, config.ExplicitToolsVersionSpecified ? config.ToolsVersion : null, _buildParameters, ((IBuildComponentHost)this).LoggingService, buildEventContext, false /* loaded by solution parser*/);
ProjectInstance[] instances = ProjectInstance.LoadSolutionForBuild(config.ProjectFullPath, config.Properties, config.ExplicitToolsVersionSpecified ? config.ToolsVersion : null, _buildParameters, ((IBuildComponentHost)this).LoggingService, buildEventContext, false /* loaded by solution parser*/, config.TargetNames);

// The first instance is the traversal project, which goes into this configuration
config.Project = instances[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ internal class BuildRequestConfiguration : IEquatable<BuildRequestConfiguration>

#endregion

/// <summary>
/// The target names that were requested to execute.
/// </summary>
internal IReadOnlyCollection<string> TargetNames { get; }

/// <summary>
/// Initializes a configuration from a BuildRequestData structure. Used by the BuildManager.
/// Figures out the correct tools version to use, falling back to the provided default if necessary.
Expand Down Expand Up @@ -172,6 +177,7 @@ internal BuildRequestConfiguration(int configId, BuildRequestData data, string d
_explicitToolsVersionSpecified = data.ExplicitToolsVersionSpecified;
_toolsVersion = ResolveToolsVersion(data, defaultToolsVersion, getToolset);
_globalProperties = data.GlobalPropertiesDictionary;
TargetNames = new List<string>(data.TargetNames);

// The following information only exists when the request is populated with an existing project.
if (data.ProjectInstance != null)
Expand Down Expand Up @@ -237,6 +243,7 @@ private BuildRequestConfiguration(int configId, BuildRequestConfiguration other)
_globalProperties = other._globalProperties;
this.IsCacheable = other.IsCacheable;
_configId = configId;
TargetNames = other.TargetNames;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

Expand Down Expand Up @@ -52,6 +55,11 @@ internal class SolutionProjectGenerator
/// </summary>
private const string SolutionConfigurationAndPlatformProperties = "Configuration=$(Configuration); Platform=$(Platform)";

/// <summary>
/// A known list of target names to create. This is for backwards compatibility.
/// </summary>
private readonly ISet<string> _knownTargetNames = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase, "Build", "Clean", "Rebuild", "Publish");

/// <summary>
/// Version 2.0
/// </summary>
Expand Down Expand Up @@ -103,6 +111,11 @@ internal class SolutionProjectGenerator
/// </summary>
private ILoggingService _loggingService;

/// <summary>
/// The list of targets specified to use.
/// </summary>
private readonly IReadOnlyCollection<string> _targetNames = new Collection<string>();

/// <summary>
/// The solution configuration selected for this build.
/// </summary>
Expand All @@ -121,14 +134,26 @@ private SolutionProjectGenerator
IDictionary<string, string> globalProperties,
string toolsVersionOverride,
BuildEventContext projectBuildEventContext,
ILoggingService loggingService
ILoggingService loggingService,
IReadOnlyCollection<string> targetNames
)
{
_solutionFile = solution;
_globalProperties = globalProperties ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
_toolsVersionOverride = toolsVersionOverride;
_projectBuildEventContext = projectBuildEventContext;
_loggingService = loggingService;

if (targetNames != null)
{
// Special target names are generated for each project like My_Project:Clean. If the user specified a value like
// that then we need to split by the first colon and assume the rest is a real target name. This works unless the
// target has a colon in it and the user is not trying to run it in a specific project. At the time of writing this
// we figured it unlikely that a target name would have a colon in it...

// The known target names are also removed from the list in case something like /t:Build was specified it is just ignored
_targetNames = targetNames.Select(i => i.Split(new[] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries).Last()).Except(_knownTargetNames, StringComparer.OrdinalIgnoreCase).ToList();
}
}

#endregion // Constructors
Expand All @@ -144,14 +169,16 @@ ILoggingService loggingService
/// <param name="toolsVersionOverride">Tools Version override (may be null). This should be any tools version explicitly passed to the command-line or from an MSBuild ToolsVersion parameter.</param>
/// <param name="projectBuildEventContext">The logging context for this project.</param>
/// <param name="loggingService">The logging service.</param>
/// <param name="targetNames">A collection of target names the user requested to be built.</param>
/// <returns>An array of ProjectInstances. The first instance is the traversal project, the remaining are the metaprojects for each project referenced in the solution.</returns>
internal static ProjectInstance[] Generate
(
SolutionFile solution,
IDictionary<string, string> globalProperties,
string toolsVersionOverride,
BuildEventContext projectBuildEventContext,
ILoggingService loggingService
ILoggingService loggingService,
IReadOnlyCollection<string> targetNames = default(IReadOnlyCollection<string>)
)
{
SolutionProjectGenerator projectGenerator = new SolutionProjectGenerator
Expand All @@ -160,7 +187,8 @@ ILoggingService loggingService
globalProperties,
toolsVersionOverride,
projectBuildEventContext,
loggingService
loggingService,
targetNames
);

return projectGenerator.Generate();
Expand Down Expand Up @@ -745,6 +773,12 @@ private void EvaluateAndAddProjects(List<ProjectInSolution> projectsInOrder, Lis
AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, "Rebuild", "BuildOutput", canBuildDirectly);
AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, "Publish", null, canBuildDirectly);

// Add any other targets specified by the user
foreach (string targetName in _targetNames)
{
AddTraversalTargetForProject(traversalInstance, project, projectConfiguration, targetName, null, canBuildDirectly);
}

// If we cannot build the project directly, then we need to generate a metaproject for it.
if (!canBuildDirectly)
{
Expand All @@ -767,6 +801,10 @@ private void AddStandardTraversalTargets(ProjectInstance traversalInstance, List
AddTraversalReferencesTarget(traversalInstance, "Clean", null);
AddTraversalReferencesTarget(traversalInstance, "Rebuild", "CollectedBuildOutput");
AddTraversalReferencesTarget(traversalInstance, "Publish", null);
foreach (string targetName in _targetNames)
{
AddTraversalReferencesTarget(traversalInstance, targetName, null);
}
}

/// <summary>
Expand Down Expand Up @@ -832,10 +870,10 @@ private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersio
// These are just dummies necessary to make the evaluation into a project instance succeed when
// any custom imported targets have declarations like BeforeTargets="Build"
// They'll be replaced momentarily with the real ones.
traversalProject.AddTarget("Build");
traversalProject.AddTarget("Rebuild");
traversalProject.AddTarget("Clean");
traversalProject.AddTarget("Publish");
foreach (string targetName in _knownTargetNames.Union(_targetNames))
{
traversalProject.AddTarget(targetName);
}

// For debugging purposes: some information is lost when evaluating into a project instance,
// so make it possible to see what we have at this point.
Expand All @@ -857,10 +895,10 @@ private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersio
);

// Make way for the real ones
traversalInstance.RemoveTarget("Build");
traversalInstance.RemoveTarget("Rebuild");
traversalInstance.RemoveTarget("Clean");
traversalInstance.RemoveTarget("Publish");
foreach (string targetName in _knownTargetNames.Union(_targetNames))
{
traversalInstance.RemoveTarget(targetName);
}

AddStandardTraversalTargets(traversalInstance, projectsInOrder);

Expand Down Expand Up @@ -1050,6 +1088,11 @@ private ProjectInstance CreateMetaproject(ProjectInstance traversalProject, Proj
AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Clean");
AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Rebuild");
AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Publish");

foreach (string targetName in _targetNames)
{
AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, targetName);
}
}
else if ((project.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) ||
(project.CanBeMSBuildProjectFile(out unknownProjectTypeErrorMessage)))
Expand All @@ -1061,13 +1104,23 @@ private ProjectInstance CreateMetaproject(ProjectInstance traversalProject, Proj
AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, null, targetOutputItemName);
AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Rebuild", targetOutputItemName);
AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Publish", null);

foreach (string targetName in _targetNames)
{
AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, targetName, null);
}
}
else
{
AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, null, unknownProjectTypeErrorMessage);
AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Clean", unknownProjectTypeErrorMessage);
AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Rebuild", unknownProjectTypeErrorMessage);
AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, "Publish", unknownProjectTypeErrorMessage);

foreach (string targetName in _targetNames)
{
AddMetaprojectTargetForUnknownProjectType(traversalProject, metaprojectInstance, project, targetName, unknownProjectTypeErrorMessage);
}
}

return metaprojectInstance;
Expand Down
12 changes: 7 additions & 5 deletions src/XMakeBuildEngine/Instance/ProjectInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1716,7 +1716,7 @@ void INodePacketTranslatable.Translate(INodePacketTranslator translator)
/// <summary>
/// Creates a set of project instances which represent the project dependency graph for a solution build.
/// </summary>
internal static ProjectInstance[] LoadSolutionForBuild(string projectFile, PropertyDictionary<ProjectPropertyInstance> globalPropertiesInstances, string toolsVersion, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext projectBuildEventContext, bool isExplicitlyLoaded)
internal static ProjectInstance[] LoadSolutionForBuild(string projectFile, PropertyDictionary<ProjectPropertyInstance> globalPropertiesInstances, string toolsVersion, BuildParameters buildParameters, ILoggingService loggingService, BuildEventContext projectBuildEventContext, bool isExplicitlyLoaded, IReadOnlyCollection<string> targetNames)
{
ErrorUtilities.VerifyThrowArgumentLength(projectFile, "projectFile");
ErrorUtilities.VerifyThrowArgumentNull(globalPropertiesInstances, "globalPropertiesInstances");
Expand Down Expand Up @@ -1748,7 +1748,7 @@ internal static ProjectInstance[] LoadSolutionForBuild(string projectFile, Prope
}
else
{
projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersion, loggingService, projectBuildEventContext);
projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersion, loggingService, projectBuildEventContext, targetNames);
}
}

Expand Down Expand Up @@ -1785,7 +1785,7 @@ internal static ProjectInstance[] LoadSolutionForBuild(string projectFile, Prope
}

string toolsVersionToUse = Utilities.GenerateToolsVersionToUse(explicitToolsVersion: null, toolsVersionFromProject: toolsVersion, getToolset: buildParameters.GetToolset, defaultToolsVersion: Constants.defaultSolutionWrapperProjectToolsVersion);
projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersionToUse, loggingService, projectBuildEventContext);
projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersionToUse, loggingService, projectBuildEventContext, targetNames);
}
}

Expand Down Expand Up @@ -2010,14 +2010,16 @@ internal void VerifyThrowNotImmutable()
/// <param name="toolsVersion">The ToolsVersion to use when generating the wrapper.</param>
/// <param name="loggingService">The logging service used to log messages etc. from the solution wrapper generator.</param>
/// <param name="projectBuildEventContext">The build event context in which this project is being constructed.</param>
/// <param name="targetNames">A collection of target names that the user requested be built.</param>
/// <returns>The ProjectRootElement for the root traversal and each of the metaprojects.</returns>
private static ProjectInstance[] GenerateSolutionWrapper
(
string projectFile,
IDictionary<string, string> globalProperties,
string toolsVersion,
ILoggingService loggingService,
BuildEventContext projectBuildEventContext
BuildEventContext projectBuildEventContext,
IReadOnlyCollection<string> targetNames
)
{
SolutionFile sp = SolutionFile.Parse(projectFile);
Expand All @@ -2035,7 +2037,7 @@ BuildEventContext projectBuildEventContext
// It's needed to determine which <UsingTask> tags to put in, whether to put a ToolsVersion parameter
// on the <MSBuild> task tags, and what MSBuildToolsPath to use when scanning child projects
// for dependency information.
ProjectInstance[] instances = SolutionProjectGenerator.Generate(sp, globalProperties, toolsVersion, projectBuildEventContext, loggingService);
ProjectInstance[] instances = SolutionProjectGenerator.Generate(sp, globalProperties, toolsVersion, projectBuildEventContext, loggingService, targetNames);
return instances;
}

Expand Down
Loading

0 comments on commit bdb4af3

Please sign in to comment.