/// <summary> /// This method generates an XmlDocument representing an MSBuild project file from the list of /// projects and project dependencies that have been collected from the solution file. /// </summary> /// <param name="solution"></param> /// <param name="msbuildProject"></param> /// <param name="toolsVersionOverride">Tools Version override (may be null). /// Any /tv:xxx switch would cause a value here.</param> /// <returns></returns> /// <owner>RGoel</owner> static internal void Generate(SolutionParser solution, Project msbuildProject, string toolsVersionOverride, BuildEventContext projectBuildEventContext) { // Validate against our minimum for upgradable projects ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile((solution.Version >= SolutionParser.slnFileMinVersion), "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solution.SolutionFile), "SolutionParseUpgradeNeeded"); // Although we only return an XmlDocument back, we need to make decisions about tools versions because // we have to choose what <UsingTask> tags to put in, whether to put a ToolsVersion parameter // on <MSBuild> task tags, and what MSBuildToolsPath to use when scanning child projects // for dependency information. string wrapperProjectToolsVersion = DetermineWrapperProjectToolsVersion(toolsVersionOverride); msbuildProject.DefaultTargets = "Build"; msbuildProject.DefaultToolsVersion = wrapperProjectToolsVersion; Engine parentEngine = msbuildProject.ParentEngine; string solutionProjectCache = solution.SolutionFile + ".cache"; bool? upToDate = LoadCache(solution, msbuildProject, projectBuildEventContext, wrapperProjectToolsVersion, parentEngine, solutionProjectCache); if (upToDate == true) { // Cache exists, was loaded, and was up to date: we're done return; } // Cache didn't exist or wasn't up to date; generate a new one Project solutionProject = msbuildProject; if (upToDate == false) { // We have already loaded a cache file we can't use; we need to work in a new project object solutionProject = CreateNewProject(solution, wrapperProjectToolsVersion, parentEngine, solutionProject); } CreateSolutionProject(solution, solutionProject, projectBuildEventContext, wrapperProjectToolsVersion, parentEngine, solutionProjectCache); if (upToDate == false) { // Put the contents of the new project object into the one we were passed msbuildProject.LoadFromXmlDocument(solutionProject.XmlDocument, projectBuildEventContext, msbuildProject.LoadSettings); } // Write a new cache file, hopefully we can use it next time UpdateCache(parentEngine, msbuildProject, solutionProjectCache, projectBuildEventContext); }
/// <summary> /// Returns a project object that matches the full path and global properties passed in. /// First, it checks our cache of building projects to see if such a project already exists. /// If so, we reuse that. Otherwise, we create a new Project object with the specified /// full path and global properties. The "existingProject" parameter passed in is just /// so we can reuse the Xml if there's already a project available with the same full path. /// </summary> /// <param name="existingProject"></param> /// <param name="projectFullPath"></param> /// <param name="globalPropertiesToUse"></param> /// <param name="buildEventContext"></param> internal Project GetMatchingProject ( Project existingProject, string projectFullPath, BuildPropertyGroup globalPropertiesToUse, string toolsVersion, string [] targetNames, BuildEventContext buildEventContext, bool toolsVersionPeekedFromProjectFile ) { // See if we already have a project with the exact same full path and global properties // that the caller is requesting us to build. If so, use that. Project returnProject = this.cacheOfBuildingProjects.GetProject(projectFullPath, globalPropertiesToUse, toolsVersion); // If this project was not found in our list, create a new project, // and load the contents from the project file. if (returnProject == null) { #if DEBUG if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDASSERTONLOADMULTIPLECOPIESOFPROJECT"))) { if (this.projectsLoadedByHost.Contains(projectFullPath)) { // We're about to load a second copy of a project that is already loaded in the IDE. // This assert can fire when different projects in the IDE have different sets of // global properties. For example, suppose there are two projects loaded in the IDE, // ProjectA and ProjectB. Suppose the project system has given ProjectA two global // properties (e.g., Configuration=Debug and Platform=AnyCPU), and has given ProjectB // three global properties (e.g., Configuration=Foobar, Platform=x86, and DevEnvDir=c:\vs). // Now, if ProjectB has a P2P reference to ProjectA, we've got a problem because when // ProjectB calls the <MSBuild> task to grab the output of ProjectA, the engine is going // to try and merge together the global properties, and the merged set will consist // of all three properties. Since we won't have a copy of ProjectA in our projectsLoadedByHost // list that has all three of the same global properties, we'll decide we have to create // a new Project object, and this is a big unnecessary perf hit (as well as being incorrect). // If a user customized his build process and is explicitly passing in Properties to the // <MSBuild> task, then we would be entering this codepath for a totally legitimate // scenario, so we don't want to disallow it. We just want to know about it if it happens // to anyone before we ship, just so we can investigate to see if there may be a bug // somewhere. if (this.projectsLoadedByHost.Count > 1) { // The assert condition (projectsLoadedByHost.Count == 1) is there because // for command-line builds using msbuild.exe, the # of projects loaded by the host will // always be exactly 1. We don't want to assert for command-line builds, because then // we'd be firing this all the time for perfectly legitimate scenarios. // We also don't want to assert in razzle, because the razzle build is expected to do this // to accomplish traversal. Debug.Assert(!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_NTROOT")), "This assertion is here to catch potential bugs wrt MSBuild and VS integration. " + "It is okay to ignore this assert only if you are in the IDE and have customized " + "your build process such that you are passing the Properties parameter into the " + "<MSBuild> task in way that causes the same project to be built with different " + "sets of global properties. However, if you are just using a standard VS-generated project, " + "this assert should not fire, so please open a bug under the \\VSCORE\\MSBuild\\VSIntegration " + "path."); } } } #endif // Check if the project has been previously unloaded due to a user request during the current build // In this case reloaded a project is an error because we can't ensure a consistent state of the reloaded project // and the cached resulted of the original string toolsVersionToUse = toolsVersion == null ? DefaultToolsVersion : toolsVersion; if (this.cacheOfBuildingProjects.HasProjectBeenLoaded(projectFullPath, globalPropertiesToUse, toolsVersionToUse)) { string joinedNames = ResourceUtilities.FormatResourceString("DefaultTargets"); if (targetNames != null && targetNames.Length > 0) { joinedNames = EscapingUtilities.UnescapeAll(String.Join(";", targetNames)); } BuildEventFileInfo fileInfo = new BuildEventFileInfo(projectFullPath); string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, "ReloadingPreviouslyUnloadedProject", projectFullPath, joinedNames); throw new InvalidProjectFileException(projectFullPath, fileInfo.Line, fileInfo.Column, fileInfo.EndLine, fileInfo.EndColumn, message, null, errorCode, helpKeyword); } // This creates a new project. try { // If the tools version was peeked from the project file then we reset the tools version to null as the tools version is read by the project object again // when setting or getting the ToolsVersion property. If the tools version was not peeked from the project file than it is an override. if (toolsVersionPeekedFromProjectFile) { toolsVersion = null; } returnProject = new Project(this, toolsVersion); } catch (InvalidOperationException) { BuildEventFileInfo fileInfo = new BuildEventFileInfo(projectFullPath); string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, "UnrecognizedToolsVersion", toolsVersion); throw new InvalidProjectFileException(projectFullPath,fileInfo.Line,fileInfo.Column,fileInfo.EndLine, fileInfo.EndColumn,message, null, errorCode,helpKeyword); } // We're only building this project ... it is not loaded by a host, and we // should feel free to discard this whenever we like. returnProject.IsLoadedByHost = false; // Give it the global properties that were requested. returnProject.GlobalProperties = globalPropertiesToUse; // Load the project file. If we don't already have an XmlDocument for this // project file, load it off disk. Otherwise, use one of the XmlDocuments // that we already have. Two advantages: 1.) perf 2.) using the in-memory // contents that the host may have altered. if (existingProject != null) { //Console.WriteLine("Reusing an existing project: " + projectFullPath); returnProject.FullFileName = projectFullPath; returnProject.LoadFromXmlDocument(existingProject.XmlDocument, buildEventContext, existingProject.LoadSettings); } else { //Console.WriteLine("Found new project: " + projectFullPath); returnProject.Load(projectFullPath, buildEventContext, ProjectLoadSettings.None); } // Add the newly created Project object to the ProjectManager. this.cacheOfBuildingProjects.AddProject(returnProject); } return returnProject; }