/// <summary> /// Set the requested project configuration. /// </summary> internal void SetProjectConfiguration(string configurationName, ProjectConfigurationInSolution configuration) { _projectConfigurations[configurationName] = configuration; }
internal void ProcessProjectConfigurationSection(Hashtable rawProjectConfigurationsEntries) { char[] chArray1 = new char[1]; int index1 = 0; int num1 = 124; chArray1[index1] = (char)num1; char[] chArray2 = chArray1; foreach (ProjectInSolution projectInSolution in this.projectsInOrder) { if (projectInSolution.ProjectType != SolutionProjectType.SolutionFolder) { foreach (ConfigurationInSolution configurationInSolution1 in this.solutionConfigurations) { CultureInfo invariantCulture1 = CultureInfo.InvariantCulture; string format1 = "{0}.{1}.ActiveCfg"; object[] objArray1 = new object[2]; int index2 = 0; string projectGuid1 = projectInSolution.ProjectGuid; objArray1[index2] = (object)projectGuid1; int index3 = 1; string fullName1 = configurationInSolution1.FullName; objArray1[index3] = (object)fullName1; string str1 = string.Format((IFormatProvider)invariantCulture1, format1, objArray1); CultureInfo invariantCulture2 = CultureInfo.InvariantCulture; string format2 = "{0}.{1}.Build.0"; object[] objArray2 = new object[2]; int index4 = 0; string projectGuid2 = projectInSolution.ProjectGuid; objArray2[index4] = (object)projectGuid2; int index5 = 1; string fullName2 = configurationInSolution1.FullName; objArray2[index5] = (object)fullName2; string str2 = string.Format((IFormatProvider)invariantCulture2, format2, objArray2); if (rawProjectConfigurationsEntries.ContainsKey((object)str1)) { string[] strArray = ((string)rawProjectConfigurationsEntries[(object)str1]).Split(chArray2); int num2 = strArray.Length <= 2 ? 1 : 0; string errorSubCategoryResourceName = "SubCategoryForSolutionParsingErrors"; BuildEventFileInfo projectFile = new BuildEventFileInfo(this.SolutionFile); string resourceName = "SolutionParseInvalidProjectSolutionConfigurationEntry"; object[] objArray3 = new object[1]; int index6 = 0; CultureInfo invariantCulture3 = CultureInfo.InvariantCulture; string format3 = "{0} = {1}"; object[] objArray4 = new object[2]; int index7 = 0; string str3 = str1; objArray4[index7] = (object)str3; int index8 = 1; object obj = rawProjectConfigurationsEntries[(object)str1]; objArray4[index8] = obj; string str4 = string.Format((IFormatProvider)invariantCulture3, format3, objArray4); objArray3[index6] = (object)str4; ProjectConfigurationInSolution configurationInSolution2 = new ProjectConfigurationInSolution(strArray[0], strArray.Length > 1 ? strArray[1] : string.Empty, rawProjectConfigurationsEntries.ContainsKey((object)str2)); projectInSolution.ProjectConfigurations[configurationInSolution1.FullName] = configurationInSolution2; } } } } }
/// <summary> /// The value to be passed to the Properties attribute of the MSBuild task to build a specific project. Contains reference to project configuration and /// platform as well as the solution configuration bits. /// </summary> private string GetPropertiesAttributeForDirectMSBuildTask(ProjectConfigurationInSolution projectConfiguration) { string directProjectProperties = OpportunisticIntern.InternStringIfPossible(String.Join(";", GetConfigurationAndPlatformPropertiesString(projectConfiguration), SolutionProperties)); return directProjectProperties; }
/// <summary> /// Returns true if the specified project can be built directly, without using a metaproject. /// </summary> private bool CanBuildDirectly(ProjectInstance traversalProject, ProjectInSolution projectToAdd, ProjectConfigurationInSolution projectConfiguration) { // Can we build this project directly, without a metaproject? We can if it's MSBuild-able and has no references building in this configuration. bool canBuildDirectly = false; string unknownProjectTypeErrorMessage; if ((projectToAdd.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) || (projectToAdd.CanBeMSBuildProjectFile(out unknownProjectTypeErrorMessage))) { canBuildDirectly = true; foreach (string dependencyProjectGuid in projectToAdd.Dependencies) { ProjectInSolution dependencyProject; if (!_solutionFile.ProjectsByGuid.TryGetValue(dependencyProjectGuid, out dependencyProject)) { ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile ( false, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(traversalProject.FullPath), "SolutionParseProjectDepNotFoundError", projectToAdd.ProjectGuid, dependencyProjectGuid ); } if (WouldProjectBuild(_solutionFile, _selectedSolutionConfiguration, dependencyProject, projectConfiguration)) { // This is a reference we would have to build, so we can't build the project directly. canBuildDirectly = false; break; } } } return canBuildDirectly; }
/// <summary> /// This method adds a new ProjectReference item to the specified instance. The reference will either be to its metaproject (if the project /// is a web project or has reference of its own) or to the project itself (if it has no references and is a normal MSBuildable project.) /// </summary> private void AddProjectReference(ProjectInstance traversalProject, ProjectInstance projectInstance, ProjectInSolution projectToAdd, ProjectConfigurationInSolution projectConfiguration, bool direct) { ProjectItemInstance item; if (direct) { // We can build this project directly, so add its reference. item = projectInstance.AddItem("ProjectReference", EscapingUtilities.Escape(projectToAdd.AbsolutePath), null); item.SetMetadata("ToolsVersion", GetToolsVersionMetadataForDirectMSBuildTask(traversalProject)); item.SetMetadata("SkipNonexistentProjects", "False"); // Skip if it doesn't exist on disk. item.SetMetadata("AdditionalProperties", GetPropertiesMetadataForProjectReference(traversalProject, GetConfigurationAndPlatformPropertiesString(projectConfiguration))); } else { // We cannot build directly, add the metaproject reference instead. item = projectInstance.AddItem("ProjectReference", GetMetaprojectName(projectToAdd), null); item.SetMetadata("ToolsVersion", traversalProject.ToolsVersion); item.SetMetadata("SkipNonexistentProjects", "Build"); // Instruct the MSBuild task to try to build even though the file doesn't exist on disk. item.SetMetadata("AdditionalProperties", GetPropertiesMetadataForProjectReference(traversalProject, SolutionConfigurationAndPlatformProperties)); } // Set raw config and platform for custom build steps to use if they wish // Configuration is null for web projects if (projectConfiguration != null) { item.SetMetadata("Configuration", projectConfiguration.ConfigurationName); item.SetMetadata("Platform", projectConfiguration.PlatformName); } }
/// <summary> /// Gets the project configuration and platform values as an attribute string for an MSBuild task used to build the project. /// </summary> private string GetConfigurationAndPlatformPropertiesString(ProjectConfigurationInSolution projectConfiguration) { string directProjectProperties = String.Format(CultureInfo.InvariantCulture, "Configuration={0}; Platform={1}", projectConfiguration.ConfigurationName, projectConfiguration.PlatformName); return directProjectProperties; }
/// <summary> /// Adds a traversal target which invokes a specified target on a single project. This creates targets called "Project", "Project:Rebuild", "Project:Clean", "Project:Publish" etc. /// </summary> private void AddTraversalTargetForProject(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, string targetToBuild, string outputItem, bool canBuildDirectly) { string baseProjectName = ProjectInSolution.DisambiguateProjectTargetName(project.GetUniqueProjectName()); string actualTargetName = baseProjectName; if (targetToBuild != null) { actualTargetName += ":" + targetToBuild; } // The output item name is the concatenation of the project name with the specified outputItem. In the typical case, if the // project name is MyProject, the outputItemName will be MyProjectBuildOutput, and the outputItemAsItem will be @(MyProjectBuildOutput). // In the case where the project contains characters not allowed as Xml element attribute values, those characters will // be replaced with underscores. In the case where MyProject is actually unrepresentable in Xml, then the // outputItemName would be _________BuildOutput. string outputItemName = null; string outputItemAsItem = null; if (!String.IsNullOrEmpty(outputItem)) { outputItemName = MakeIntoSafeItemName(baseProjectName) + outputItem; outputItemAsItem = "@(" + outputItemName + ")"; } ProjectTargetInstance targetElement = traversalProject.AddTarget(actualTargetName, null, null, outputItemAsItem, null, null, null, false /* legacy target returns behaviour */); if (canBuildDirectly) { AddProjectBuildTask(traversalProject, project, projectConfiguration, targetElement, targetToBuild, "@(ProjectReference)", "'%(ProjectReference.Identity)' == '" + EscapingUtilities.Escape(project.AbsolutePath) + "'", outputItemName); } else { AddMetaprojectBuildTask(traversalProject, project, targetElement, targetToBuild, outputItemName); } }
/// <summary> /// Returns true if the specified project will build in the currently selected solution configuration. /// </summary> private static bool WouldProjectBuild(SolutionFile solutionFile, string selectedSolutionConfiguration, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration) { if (projectConfiguration == null) { if (project.ProjectType == SolutionProjectType.WebProject) { // Sometimes web projects won't have the configuration we need (Release typically.) But they should still build if there is // a solution configuration for it foreach (SolutionConfigurationInSolution configuration in solutionFile.SolutionConfigurations) { if (String.Equals(configuration.FullName, selectedSolutionConfiguration, StringComparison.OrdinalIgnoreCase)) { return true; } } } // No configuration, so it can't build. return false; } if (!projectConfiguration.IncludeInBuild) { // Not included in the build. return false; } return true; }
/// <summary> /// Adds an MSBuild task to a real project. /// </summary> private void AddProjectBuildTask(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, ProjectTargetInstance target, string targetToBuild, string sourceItems, string condition, string outputItem) { ProjectTaskInstance task = target.AddTask("MSBuild", condition, String.Empty); task.SetParameter("Projects", sourceItems); if (targetToBuild != null) { task.SetParameter("Targets", targetToBuild); } task.SetParameter("BuildInParallel", "True"); task.SetParameter("ToolsVersion", GetToolsVersionAttributeForDirectMSBuildTask(traversalProject)); task.SetParameter("Properties", GetPropertiesAttributeForDirectMSBuildTask(projectConfiguration)); if (outputItem != null) { task.AddOutputItem("TargetOutputs", outputItem, String.Empty); } }
/// <summary> /// Adds the targets which build the dependencies and actual project for a metaproject. /// </summary> private void AddMetaprojectTargetForManagedProject(ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration, string targetName, string outputItem) { string outputItemAsItem = null; if (!String.IsNullOrEmpty(outputItem)) { outputItemAsItem = "@(" + outputItem + ")"; } ProjectTargetInstance target = metaprojectInstance.AddTarget(targetName ?? "Build", String.Empty, String.Empty, outputItemAsItem, null, String.Empty, String.Empty, false /* legacy target returns behaviour */); AddReferencesBuildTask(metaprojectInstance, target, targetName, outputItem); // Add the task to build the actual project. AddProjectBuildTask(traversalProject, project, projectConfiguration, target, targetName, EscapingUtilities.Escape(project.AbsolutePath), String.Empty, outputItem); }
/// <summary> /// Produces a set of targets which allows the MSBuild scheduler to schedule projects in the order automatically by /// following their dependencies without enforcing build levels. /// </summary> /// <remarks> /// We want MSBuild to be able to parallelize the builds of these projects where possible and still honor references. /// Since the project files referenced by the solution do not (necessarily) themselves contain actual project references /// to the projects they depend on, we need to synthesize this relationship ourselves. This is done by creating a target /// which first invokes the project's dependencies, then invokes the actual project itself. However, invoking the /// dependencies must also invoke their dependencies and so on down the line. /// /// Additionally, we do not wish to create a separate MSBuild project to contain this target yet we want to parallelize /// calls to these targets. The way to do this is to pass in different global properties to the same project in the same /// MSBuild call. MSBuild easily allows this using the AdditionalProperties metadata which can be specified on an Item. /// /// Assuming the solution project we are generating is called "foo.proj", we can accomplish this parallelism as follows: /// <ItemGroup> /// <ProjectReference Include="Project0"/> /// <ProjectReference Include="Project1"/> /// <ProjectReference Include="Project2"/> /// </ItemGroup> /// /// We now have expressed the top level reference to all projects as @(SolutionReference) and each project's /// set of references as @(PROJECTNAMEReference). We construct our target as: /// /// <Target Name="Build"> /// <MSBuild Projects="@(ProjectReference)" Targets="Build" /> /// <MSBuild Projects="actualProjectName" Targets="Build" /> /// </Target> /// /// The first MSBuild call re-invokes the solution project instructing it to build the reference projects for the /// current project. The second MSBuild call invokes the actual project itself. Because all reference projects have /// the same additional properties, MSBuild will only build the first one it comes across and the rest will be /// satisfied from the cache. /// </remarks> private ProjectInstance CreateMetaproject(ProjectInstance traversalProject, ProjectInSolution project, ProjectConfigurationInSolution projectConfiguration) { // Create a new project instance with global properties and tools version from the existing project ProjectInstance metaprojectInstance = new ProjectInstance(EscapingUtilities.UnescapeAll(GetMetaprojectName(project)), traversalProject, GetMetaprojectGlobalProperties(traversalProject)); // Add the project references which must build before this one. AddMetaprojectReferenceItems(traversalProject, metaprojectInstance, project); // This string holds the error message generated when we try to determine if a project is an MSBuild format // project but it is not. string unknownProjectTypeErrorMessage; if (project.ProjectType == SolutionProjectType.WebProject) { AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, null); AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Clean"); AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Rebuild"); AddMetaprojectTargetForWebProject(traversalProject, metaprojectInstance, project, "Publish"); } else if ((project.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) || (project.CanBeMSBuildProjectFile(out unknownProjectTypeErrorMessage))) { string safeItemNameFromProjectName = MakeIntoSafeItemName(project.ProjectName); string targetOutputItemName = string.Format(CultureInfo.InvariantCulture, "{0}BuildOutput", safeItemNameFromProjectName); AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Clean", null); AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, null, targetOutputItemName); AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Rebuild", targetOutputItemName); AddMetaprojectTargetForManagedProject(traversalProject, metaprojectInstance, project, projectConfiguration, "Publish", 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); } return metaprojectInstance; }
/// <summary> /// Read the project configuration information for every project in the solution, using pre-cached /// solution section data. /// </summary> /// <param name="rawProjectConfigurationsEntries">Cached data from the project configuration section</param> internal void ProcessProjectConfigurationSection(Hashtable rawProjectConfigurationsEntries) { // Instead of parsing the data line by line, we parse it project by project, constructing the // entry name (e.g. "{A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Release|Any CPU.ActiveCfg") and retrieving its // value from the raw data. The reason for this is that the IDE does it this way, and as the result // the '.' character is allowed in configuration names although it technically separates different // parts of the entry name string. This could lead to ambiguous results if we tried to parse // the entry name instead of constructing it and looking it up. Although it's pretty unlikely that // this would ever be a problem, it's safer to do it the same way VS IDE does it. char[] configPlatformSeparators = new char[] { SolutionConfigurationInSolution.ConfigurationPlatformSeparator }; foreach (ProjectInSolution project in _projectsInOrder) { // Solution folders don't have configurations if (project.ProjectType != SolutionProjectType.SolutionFolder) { foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionConfigurations) { // The "ActiveCfg" entry defines the active project configuration in the given solution configuration // This entry must be present for every possible solution configuration/project combination. string entryNameActiveConfig = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.ActiveCfg", project.ProjectGuid, solutionConfiguration.FullName); // The "Build.0" entry tells us whether to build the project configuration in the given solution configuration. // Technically, it specifies a configuration name of its own which seems to be a remnant of an initial, // more flexible design of solution configurations (as well as the '.0' suffix - no higher values are ever used). // The configuration name is not used, and the whole entry means "build the project configuration" // if it's present in the solution file, and "don't build" if it's not. string entryNameBuild = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.Build.0", project.ProjectGuid, solutionConfiguration.FullName); if (rawProjectConfigurationsEntries.ContainsKey(entryNameActiveConfig)) { string[] configurationPlatformParts = ((string)(rawProjectConfigurationsEntries[entryNameActiveConfig])).Split(configPlatformSeparators); // Project configuration may not necessarily contain the platform part. Some project support only the configuration part. ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(configurationPlatformParts.Length <= 2, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath), "SolutionParseInvalidProjectSolutionConfigurationEntry", string.Format(CultureInfo.InvariantCulture, "{0} = {1}", entryNameActiveConfig, rawProjectConfigurationsEntries[entryNameActiveConfig])); ProjectConfigurationInSolution projectConfiguration = new ProjectConfigurationInSolution( configurationPlatformParts[0], (configurationPlatformParts.Length > 1) ? configurationPlatformParts[1] : string.Empty, rawProjectConfigurationsEntries.ContainsKey(entryNameBuild) ); project.SetProjectConfiguration(solutionConfiguration.FullName, projectConfiguration); } } } } }