/// <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);

        }
Exemple #2
0
        /// <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;
        }