/// <summary> /// This method is used to flag errors in the project file being processed. Do NOT use this method in place of /// ErrorUtilities.VerifyThrow(), because ErrorUtilities.VerifyThrow() is used to flag internal/programming errors. /// /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios /// </summary> /// <owner>SumedhK</owner> /// <param name="condition">The condition to check.</param> /// <param name="errorSubCategoryResourceName">The resource string for the error sub-category (can be null).</param> /// <param name="projectFile">The invalid project file.</param> /// <param name="resourceName">The resource string for the error message.</param> /// <param name="args">Extra arguments for formatting the error message.</param> internal static void VerifyThrowInvalidProjectFile ( bool condition, string errorSubCategoryResourceName, BuildEventFileInfo projectFile, string resourceName, params object[] args ) { ErrorUtilities.VerifyThrow(projectFile != null, "Must specify the invalid project file. If project file is not available, use VerifyThrowInvalidProject() and pass in the XML node instead."); #if DEBUG if (errorSubCategoryResourceName != null) { ResourceUtilities.VerifyResourceStringExists(errorSubCategoryResourceName); } ResourceUtilities.VerifyResourceStringExists(resourceName); #endif if (!condition) { string errorSubCategory = null; if (errorSubCategoryResourceName != null) { errorSubCategory = AssemblyResources.GetString(errorSubCategoryResourceName); } string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, resourceName, args); throw new InvalidProjectFileException(projectFile.File, projectFile.Line, projectFile.Column, projectFile.EndLine, projectFile.EndColumn, message, errorSubCategory, errorCode, helpKeyword); } }
/// <summary> /// This method is used to flag errors in the project file being processed. Do NOT use this method in place of /// ErrorUtilities.VerifyThrow(), because ErrorUtilities.VerifyThrow() is used to flag internal/programming errors. /// /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios /// </summary> /// <owner>SumedhK</owner> /// <param name="condition">The condition to check.</param> /// <param name="projectFile">The invalid project file.</param> /// <param name="resourceName">The resource string for the error message.</param> /// <param name="args">Extra arguments for formatting the error message.</param> internal static void VerifyThrowInvalidProjectFile ( bool condition, BuildEventFileInfo projectFile, string resourceName, params object[] args ) { VerifyThrowInvalidProjectFile(condition, null, projectFile, resourceName, args); }
/// <summary> /// If there is an exception in process build request we will wrap it in an invalid project file exception as any exceptions caught here are really problems with a project file /// this exception will be handled in the engine and logged /// </summary> private static void ConvertToInvalidProjectException(BuildRequest buildRequest, Project parentProject, Exception e) { BuildEventFileInfo fileInfo = new BuildEventFileInfo(buildRequest.ProjectFileName); throw new InvalidProjectFileException(parentProject.FullFileName, fileInfo.Line, fileInfo.Column, fileInfo.EndLine, fileInfo.EndColumn, e.Message, null, null, null); }
/// <summary> /// Logs a warning with all registered loggers using the given text. /// </summary> /// <owner>SumedhK</owner> /// <param name="subcategoryResourceName">Can be null.</param> /// <param name="warningCode">Can be null.</param> /// <param name="helpKeyword">Can be null.</param> /// <param name="file"></param> /// <param name="message"></param> virtual internal void LogWarningFromText(BuildEventContext buildEventContext, string subcategoryResourceName, string warningCode, string helpKeyword, BuildEventFileInfo file, string message) { ErrorUtilities.VerifyThrow(file != null, "Must specify the associated file."); ErrorUtilities.VerifyThrow(message != null, "Need warning message."); string subcategory = null; if (subcategoryResourceName != null) { subcategory = AssemblyResources.GetString(subcategoryResourceName); } BuildWarningEventArgs e = new BuildWarningEventArgs ( subcategory, warningCode, file.File, file.Line, file.Column, file.EndLine, file.EndColumn, message, helpKeyword, "MSBuild" ); e.BuildEventContext = buildEventContext; PostLoggingEvent(e); }
/// <summary> /// Logs a warning with all registered loggers using the specified resource string. /// </summary> /// <owner>SumedhK</owner> /// <param name="subcategoryResourceName">Can be null.</param> /// <param name="file"></param> /// <param name="messageResourceName"></param> /// <param name="messageArgs"></param> virtual internal void LogWarning(BuildEventContext buildEventContext, string subcategoryResourceName, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs) { ErrorUtilities.VerifyThrow(messageResourceName != null, "Need resource string for warning message."); string warningCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out warningCode, out helpKeyword, messageResourceName, messageArgs); LogWarningFromText(buildEventContext, subcategoryResourceName, warningCode, helpKeyword, file, message); }
/// <summary> /// Logs a warning with all registered loggers using the specified resource string. /// </summary> /// <owner>SumedhK</owner> /// <param name="file"></param> /// <param name="messageResourceName"></param> /// <param name="messageArgs"></param> virtual internal void LogWarning(BuildEventContext buildEventContext, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs) { LogWarning(buildEventContext,null, file, messageResourceName, messageArgs); }
/************************************************************************************************************************** * WARNING: Do not add overloads that allow raising events without specifying a file. In general ALL events should have a * file associated with them. We've received a LOT of feedback from dogfooders about the lack of information in our * events. If an event TRULY does not have an associated file, then String.Empty can be passed in for the file. However, * that burden should lie on the caller -- these wrapper methods should NOT make it easy to skip the filename. *************************************************************************************************************************/ /// <summary> /// Logs an warning regarding an unexpected task failure with all registered loggers. /// This will include a stack dump. /// </summary> /// <owner>RGoel</owner> /// <param name="exception"></param> /// <param name="file"></param> /// <param name="taskName"></param> virtual internal void LogTaskWarningFromException(BuildEventContext buildEventContext, Exception exception, BuildEventFileInfo file, string taskName) { ErrorUtilities.VerifyThrow(taskName != null, "Must specify the name of the task that failed."); ErrorUtilities.VerifyThrow(file != null, "Must specify the associated file."); string warningCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out warningCode, out helpKeyword, "FatalTaskError", taskName); #if DEBUG message += Environment.NewLine + "This is an unhandled exception -- PLEASE OPEN A BUG."; #endif if (exception != null) { message += Environment.NewLine + exception.ToString(); } LogWarningFromText(buildEventContext, null, warningCode, helpKeyword, file, message); }
/// <summary> /// Logs an error regarding an unexpected failure with all registered loggers using the specified resource string. /// This will include a stack dump. /// </summary> /// <owner>SumedhK</owner> /// <param name="exception"></param> /// <param name="file"></param> /// <param name="messageResourceName"></param> /// <param name="messageArgs"></param> virtual internal void LogFatalError(BuildEventContext buildEventContext, Exception exception, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs) { ErrorUtilities.VerifyThrow(messageResourceName != null, "Need resource string for error message."); string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, messageResourceName, messageArgs); #if DEBUG message += Environment.NewLine + "This is an unhandled exception -- PLEASE OPEN A BUG."; #endif if (exception != null) { message += Environment.NewLine + exception.ToString(); } LogErrorFromText(buildEventContext, null, errorCode, helpKeyword, file, message); }
/// <summary> /// Logs an error regarding an unexpected build failure with all registered loggers. /// This will include a stack dump. /// </summary> /// <owner>SumedhK</owner> /// <param name="exception"></param> /// <param name="file"></param> virtual internal void LogFatalBuildError(BuildEventContext buildEventContext, Exception exception, BuildEventFileInfo file) { LogFatalError(buildEventContext, exception, file, "FatalBuildError"); }
/************************************************************************************************************************** * WARNING: Do not add overloads that allow raising events without specifying a file. In general ALL events should have a * file associated with them. We've received a LOT of feedback from dogfooders about the lack of information in our * events. If an event TRULY does not have an associated file, then String.Empty can be passed in for the file. However, * that burden should lie on the caller -- these wrapper methods should NOT make it easy to skip the filename. *************************************************************************************************************************/ /// <summary> /// Logs an error with all registered loggers using the specified resource string. /// </summary> /// <owner>SumedhK</owner> /// <param name="file"></param> /// <param name="messageResourceName"></param> /// <param name="messageArgs"></param> virtual internal void LogError(BuildEventContext location, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs) { LogError(location, null, file, messageResourceName, messageArgs); }
/// <summary> /// Loads the XML for the specified project that is being imported into the main project. /// </summary> /// <owner>RGoel, SumedhK</owner> /// <param name="import">The project being imported</param> /// <returns>XML for imported project; null, if duplicate import.</returns> private XmlDocument LoadImportedProject(Import import) { XmlDocument importedDocument = null; bool importedFileExists = File.Exists(import.EvaluatedProjectPath); // NOTE: don't use ErrorUtilities.VerifyThrowFileExists() here because that exception doesn't carry XML node // information, and we need that data to show a better error message if (!importedFileExists) { ProjectErrorUtilities.VerifyThrowInvalidProject((this.loadSettings & ProjectLoadSettings.IgnoreMissingImports) != 0, import.ProjectPathAttribute, "ImportedProjectNotFound", import.EvaluatedProjectPath); } // Make sure that the file we're about to import hasn't been imported previously. // This is how we prevent circular dependencies. It so happens that this mechanism // also prevents the same file from being imported twice, even it it's not a // circular dependency, but that's fine -- no good reason to do that anyway. if ((this.imports[import.EvaluatedProjectPath] != null) || (string.Compare(this.FullFileName, import.EvaluatedProjectPath, StringComparison.OrdinalIgnoreCase) == 0)) { ParentEngine.LoggingServices.LogWarning(projectBuildEventContext, Utilities.CreateBuildEventFileInfo(import.ProjectPathAttribute, FullFileName), "DuplicateImport", import.EvaluatedProjectPath); } else { // See if the imported project is also a top-level project that has been loaded // by the engine. If so, use the in-memory copy of the imported project instead // of reading the copy off of the disk. This way, we can reflect any changes // that have been made to the in-memory copy. Project importedProject = this.ParentEngine.GetLoadedProject(import.EvaluatedProjectPath); if (importedProject != null) { importedDocument = importedProject.XmlDocument; } // The imported project is not part of the engine, so read it off of disk. else { // If the file doesn't exist on disk but we're told to ignore missing imports, simply skip it if (importedFileExists) { // look up the engine's cache to see if we've already loaded this imported project on behalf of another // top-level project ImportedProject previouslyImportedProject = (ImportedProject)ParentEngine.ImportedProjectsCache[import.EvaluatedProjectPath]; // if this project hasn't been imported before, or if it has changed on disk, we need to load it if ((previouslyImportedProject == null) || previouslyImportedProject.HasChangedOnDisk(import.EvaluatedProjectPath)) { try { // Do not validate the imported file against a schema. // We only validate the parent project against a schema in V1, because without custom // namespace support, we would have to pollute the msbuild namespace with everything that // appears anywhere in our targets file. // cache this imported project, so that if another top-level project also imports this project, we // will not re-parse the XML (unless it changes) previouslyImportedProject = new ImportedProject(import.EvaluatedProjectPath); ParentEngine.ImportedProjectsCache[import.EvaluatedProjectPath] = previouslyImportedProject; } // catch XML exceptions early so that we still have the imported project file name catch (XmlException e) { BuildEventFileInfo fileInfo = new BuildEventFileInfo(e); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, fileInfo, "InvalidImportedProjectFile", e.Message); } // catch IO exceptions, for example for when the file is in use. DDB #36839 catch (Exception e) { if (ExceptionHandling.NotExpectedException(e)) throw; BuildEventFileInfo fileInfo = new BuildEventFileInfo(import.EvaluatedProjectPath); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, fileInfo, "InvalidImportedProjectFile", e.Message); } } importedDocument = previouslyImportedProject.Xml; } } // Add the imported filename to our list, so we can be sure not to import // it again. This helps prevent infinite recursion. this.imports[import.EvaluatedProjectPath] = import; } return importedDocument; }
/// <summary> /// Reads in the contents of this project from a string containing the Xml contents. /// </summary> /// <exception cref="InvalidProjectFileException"></exception> public void LoadXml ( string projectXml, ProjectLoadSettings projectLoadSettings ) { ErrorUtilities.VerifyThrowArgumentNull(projectXml, "projectXml"); try { XmlDocument projectDocument = new XmlDocument(); // XmlDocument.Load() may throw an XmlException projectDocument.LoadXml(projectXml); InternalLoadFromXmlDocument(projectDocument, projectLoadSettings); // This means that as far as we know, this project hasn't been saved to disk yet. this.dirtyNeedToSaveProjectFile = true; } // handle errors in project syntax catch (InvalidProjectFileException e) { ParentEngine.LoggingServices.LogInvalidProjectFileError(projectBuildEventContext, e); throw; } // handle XML parsing errors (when reading XML contents) catch (XmlException e) { BuildEventFileInfo fileInfo = new BuildEventFileInfo(e); ParentEngine.LoggingServices.LogError(projectBuildEventContext, null,fileInfo, "InvalidProjectFile", e.Message); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, fileInfo, "InvalidProjectFile", e.Message); } }
/// <summary> /// Reads in the contents of this project from a project XML file on disk. /// </summary> /// <exception cref="InvalidProjectFileException"></exception> internal void Load ( string projectFileName, BuildEventContext buildEventContext, ProjectLoadSettings projectLoadSettings ) { ErrorUtilities.VerifyThrowArgumentNull(projectFileName, "projectFileName"); ErrorUtilities.VerifyThrowArgument(projectFileName.Length > 0, "EmptyProjectFileName"); ErrorUtilities.VerifyThrowArgument(File.Exists(projectFileName), "ProjectFileNotFound", projectFileName); #if (!STANDALONEBUILD) using (new CodeMarkerStartEnd(CodeMarkerEvent.perfMSBuildProjectLoadFromFileBegin, CodeMarkerEvent.perfMSBuildProjectLoadFromFileEnd)) #endif { string projectFullFileName = Path.GetFullPath(projectFileName); try { #if MSBUILDENABLEVSPROFILING string beginProjectLoad = String.Format(CultureInfo.CurrentCulture, "Load Project {0} Using Old OM - Start", projectFullFileName); DataCollection.CommentMarkProfile(8806, beginProjectLoad); #endif XmlDocument projectDocument = null; if (IsSolutionFilename(projectFileName)) { SolutionParser sp = new SolutionParser(); sp.SolutionFile = projectFileName; sp.ParseSolutionFile(); // Log any comments from the solution parser if (sp.SolutionParserComments.Count > 0) { foreach (string comment in sp.SolutionParserComments) { ParentEngine.LoggingServices.LogCommentFromText(buildEventContext, MessageImportance.Low, comment); } } // Pass the toolsVersion of this project through, which will be not null if there was a /tv:nn switch // Although we only get an XmlDocument, not a Project object back, it's still needed // to determine which <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. SolutionWrapperProject.Generate(sp, this, toolsVersion, buildEventContext); } else if (IsVCProjFilename(projectFileName)) { ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, new BuildEventFileInfo(projectFileName), "ProjectUpgradeNeededToVcxProj", projectFileName); } else { projectDocument = new XmlDocument(); // XmlDocument.Load() may throw an XmlException projectDocument.Load(projectFileName); } // Setting the FullFileName causes this project to be "registered" with // the engine. (Well, okay, "registered" is the wrong word ... but the // engine starts keeping close track of this project in its tables.) // We want to avoid this until we're sure that the XML is valid and // the document can be read in. Bug VSWhidbey 415236. this.FullFileName = projectFullFileName; if (!IsSolutionFilename(projectFileName)) { InternalLoadFromXmlDocument(projectDocument, projectLoadSettings); } // This project just came off the disk, so it is certainly not dirty yet. this.dirtyNeedToSaveProjectFile = false; } // handle errors in project syntax catch (InvalidProjectFileException e) { ParentEngine.LoggingServices.LogInvalidProjectFileError(buildEventContext, e); throw; } // handle errors in path resolution catch (SecurityException e) { ParentEngine.LoggingServices.LogError(buildEventContext, new BuildEventFileInfo(FullFileName), "InvalidProjectFile", e.Message); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, new BuildEventFileInfo(FullFileName), "InvalidProjectFile", e.Message); } // handle errors in path resolution catch (NotSupportedException e) { ParentEngine.LoggingServices.LogError(buildEventContext, new BuildEventFileInfo(FullFileName), "InvalidProjectFile", e.Message); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, new BuildEventFileInfo(FullFileName), "InvalidProjectFile", e.Message); } // handle errors in loading project file catch (IOException e) { ParentEngine.LoggingServices.LogError(buildEventContext, new BuildEventFileInfo(FullFileName), "InvalidProjectFile", e.Message); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, new BuildEventFileInfo(FullFileName), "InvalidProjectFile", e.Message); } // handle errors in loading project file catch (UnauthorizedAccessException e) { ParentEngine.LoggingServices.LogError(buildEventContext, new BuildEventFileInfo(FullFileName), "InvalidProjectFile", e.Message); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, new BuildEventFileInfo(FullFileName), "InvalidProjectFile", e.Message); } // handle XML parsing errors (when reading project file) catch (XmlException e) { BuildEventFileInfo fileInfo = new BuildEventFileInfo(e); ParentEngine.LoggingServices.LogError(buildEventContext, fileInfo, "InvalidProjectFile", e.Message); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, fileInfo, "InvalidProjectFile", e.Message); } finally { // Flush the logging queue ParentEngine.LoggingServices.ProcessPostedLoggingEvents(); #if MSBUILDENABLEVSPROFILING DataCollection.CommentMarkProfile(8807, "Load Project Using Old OM - End"); #endif } } }
/// <summary> /// This method generates an XmlDocument representing an MSBuild project wrapper for a VC project /// </summary> /// <owner>LukaszG</owner> static internal XmlDocument GenerateVCWrapperProject(Engine parentEngine, string vcProjectFilename, string toolsVersion) { string projectPath = Path.GetFullPath(vcProjectFilename); Project msbuildProject = null; try { msbuildProject = new Project(parentEngine, toolsVersion); } catch (InvalidOperationException) { BuildEventFileInfo fileInfo = new BuildEventFileInfo(projectPath); string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, "UnrecognizedToolsVersion", toolsVersion); throw new InvalidProjectFileException(projectPath, fileInfo.Line, fileInfo.Column, fileInfo.EndLine, fileInfo.EndColumn, message, null, errorCode, helpKeyword); } msbuildProject.IsLoadedByHost = false; msbuildProject.DefaultTargets = "Build"; string wrapperProjectToolsVersion = SolutionWrapperProject.DetermineWrapperProjectToolsVersion(toolsVersion); msbuildProject.DefaultToolsVersion = wrapperProjectToolsVersion; BuildPropertyGroup propertyGroup = msbuildProject.AddNewPropertyGroup(true /* insertAtEndOfProject = true */); propertyGroup.Condition = " ('$(Configuration)' != '') and ('$(Platform)' == '') "; propertyGroup.AddNewProperty("ConfigurationName", "$(Configuration)"); propertyGroup = msbuildProject.AddNewPropertyGroup(true /* insertAtEndOfProject = true */); propertyGroup.Condition = " ('$(Configuration)' != '') and ('$(Platform)' != '') "; propertyGroup.AddNewProperty("ConfigurationName", "$(Configuration)|$(Platform)"); // only use PlatformName if we only have the platform part propertyGroup = msbuildProject.AddNewPropertyGroup(true /* insertAtEndOfProject = true */); propertyGroup.Condition = " ('$(Configuration)' == '') and ('$(Platform)' != '') "; propertyGroup.AddNewProperty("PlatformName", "$(Platform)"); AddVCBuildTarget(msbuildProject, projectPath, "Build", null); AddVCBuildTarget(msbuildProject, projectPath, "Clean", "Clean"); AddVCBuildTarget(msbuildProject, projectPath, "Rebuild", "Rebuild"); AddVCBuildTarget(msbuildProject, projectPath, "Publish", "Publish"); // Special environment variable to allow people to see the in-memory MSBuild project generated // to represent the VC project. if (Environment.GetEnvironmentVariable("MSBuildEmitSolution") != null) { msbuildProject.Save(vcProjectFilename + ".proj"); } return msbuildProject.XmlDocument; }
/// <summary> /// Logs an error regarding an unexpected task failure with all registered loggers. /// This will include a stack dump. /// </summary> /// <owner>SumedhK</owner> /// <param name="exception"></param> /// <param name="file"></param> /// <param name="taskName"></param> virtual internal void LogFatalTaskError(BuildEventContext buildEventContext, Exception exception, BuildEventFileInfo file, string taskName) { ErrorUtilities.VerifyThrow(taskName != null, "Must specify the name of the task that failed."); LogFatalError(buildEventContext, exception, file, "FatalTaskError", taskName); }
/// <summary> /// Create a new project to construct a solution wrapper cache inside /// </summary> private static Project CreateNewProject(SolutionParser solution, string wrapperProjectToolsVersion, Engine parentEngine, Project solutionProject) { try { solutionProject = new Project(parentEngine, wrapperProjectToolsVersion); solutionProject.DefaultTargets = "Build"; solutionProject.DefaultToolsVersion = wrapperProjectToolsVersion; solutionProject.IsLoadedByHost = false; } catch (InvalidOperationException) { BuildEventFileInfo fileInfo = new BuildEventFileInfo(solution.SolutionFile); string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, "UnrecognizedToolsVersion", wrapperProjectToolsVersion); throw new InvalidProjectFileException(solution.SolutionFile, fileInfo.Line, fileInfo.Column, fileInfo.EndLine, fileInfo.EndColumn, message, null, errorCode, helpKeyword); } return solutionProject; }
/// <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; }