/// <summary> /// Ensure we have the data. /// Does not throw for nonexistence. /// </summary> private void EnsurePopulated() { if (_dataIsGood == null) { _dataIsGood = false; _filename = FileUtilities.AttemptToShortenPath(_filename); // This is no-op unless the path actually is too long _data = new NativeMethodsShared.WIN32_FILE_ATTRIBUTE_DATA(); // THIS COPIED FROM THE BCL: // // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. int oldMode = NativeMethodsShared.SetErrorMode(1 /* ErrorModes.SEM_FAILCRITICALERRORS */); bool success = false; _fileOrDirectoryExists = true; try { success = NativeMethodsShared.GetFileAttributesEx(_filename, 0, ref _data); if (!success) { int error = Marshal.GetLastWin32Error(); // File not found is the most common case, for example we're copying // somewhere without a file yet. Don't do something like FileInfo.Exists to // get a nice error, or we're doing IO again! Don't even format our own string: // that turns out to be unacceptably expensive here as well. Set a flag for this particular case. // // Also, when not under debugger (!) it will give error == 3 for path too long. Make that consistently throw instead. if ((error == 2 /* ERROR_FILE_NOT_FOUND */ || error == 3 /* ERROR_PATH_NOT_FOUND */) && _filename.Length <= NativeMethodsShared.MAX_PATH) { _fileOrDirectoryExists = false; return; } // Throw nice message as far as we can. At this point IO is OK. var length = new FileInfo(_filename).Length; // Otherwise this will give at least something NativeMethodsShared.ThrowExceptionForErrorCode(error); ErrorUtilities.ThrowInternalErrorUnreachable(); } } finally { NativeMethodsShared.SetErrorMode(oldMode); } _dataIsGood = true; } }
internal static async Task <bool> ExecuteTargets( ITaskItem[] projects, Dictionary <string, string> propertiesTable, string[] undefineProperties, List <string[]> targetLists, bool stopOnFirstFailure, bool rebaseOutputs, IBuildEngine3 buildEngine, TaskLoggingHelper log, List <ITaskItem> targetOutputs, bool unloadProjectsOnCompletion, string toolsVersion, bool skipNonexistentTargets) { bool success = true; // We don't log a message about the project and targets we're going to // build, because it'll all be in the immediately subsequent ProjectStarted event. var projectDirectory = new string[projects.Length]; var projectNames = new string[projects.Length]; var toolsVersions = new string[projects.Length]; var projectProperties = new Dictionary <string, string> [projects.Length]; var undefinePropertiesPerProject = new List <string> [projects.Length]; for (int i = 0; i < projectNames.Length; i++) { projectNames[i] = null; projectProperties[i] = propertiesTable; if (projects[i] != null) { // Retrieve projectDirectory only the first time. It never changes anyway. string projectPath = FileUtilities.AttemptToShortenPath(projects[i].ItemSpec); projectDirectory[i] = Path.GetDirectoryName(projectPath); projectNames[i] = projects[i].ItemSpec; toolsVersions[i] = toolsVersion; // If the user specified a different set of global properties for this project, then // parse the string containing the properties if (!String.IsNullOrEmpty(projects[i].GetMetadata("Properties"))) { if (!PropertyParser.GetTableWithEscaping (log, ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.OverridingProperties", projectNames[i]), "Properties", projects[i].GetMetadata("Properties").Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries), out Dictionary <string, string> preProjectPropertiesTable) ) { return(false); } projectProperties[i] = preProjectPropertiesTable; } if (undefineProperties != null) { undefinePropertiesPerProject[i] = new List <string>(undefineProperties); } // If the user wanted to undefine specific global properties for this project, parse // that string and remove them now. string projectUndefineProperties = projects[i].GetMetadata("UndefineProperties"); if (!String.IsNullOrEmpty(projectUndefineProperties)) { string[] propertiesToUndefine = projectUndefineProperties.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); if (undefinePropertiesPerProject[i] == null) { undefinePropertiesPerProject[i] = new List <string>(propertiesToUndefine.Length); } if (log != null && propertiesToUndefine.Length > 0) { log.LogMessageFromResources(MessageImportance.Low, "General.ProjectUndefineProperties", projectNames[i]); foreach (string property in propertiesToUndefine) { undefinePropertiesPerProject[i].Add(property); log.LogMessageFromText(String.Format(CultureInfo.InvariantCulture, " {0}", property), MessageImportance.Low); } } } // If the user specified a different set of global properties for this project, then // parse the string containing the properties if (!String.IsNullOrEmpty(projects[i].GetMetadata("AdditionalProperties"))) { if (!PropertyParser.GetTableWithEscaping (log, ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.AdditionalProperties", projectNames[i]), "AdditionalProperties", projects[i].GetMetadata("AdditionalProperties").Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries), out Dictionary <string, string> additionalProjectPropertiesTable) ) { return(false); } var combinedTable = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); // First copy in the properties from the global table that not in the additional properties table if (projectProperties[i] != null) { foreach (KeyValuePair <string, string> entry in projectProperties[i]) { if (!additionalProjectPropertiesTable.ContainsKey(entry.Key)) { combinedTable.Add(entry.Key, entry.Value); } } } // Add all the additional properties foreach (KeyValuePair <string, string> entry in additionalProjectPropertiesTable) { combinedTable.Add(entry.Key, entry.Value); } projectProperties[i] = combinedTable; } // If the user specified a different toolsVersion for this project - then override the setting if (!String.IsNullOrEmpty(projects[i].GetMetadata("ToolsVersion"))) { toolsVersions[i] = projects[i].GetMetadata("ToolsVersion"); } } } foreach (string[] targetList in targetLists) { if (stopOnFirstFailure && !success) { // Inform the user that we skipped the remaining targets StopOnFirstFailure=true. log.LogMessageFromResources(MessageImportance.Low, "MSBuild.SkippingRemainingTargets"); // We have encountered a failure. Caller has requested that we not // continue with remaining targets. break; } // Send the project off to the build engine. By passing in null to the // first param, we are indicating that the project to build is the same // as the *calling* project file. var taskHost = (TaskHost)buildEngine; BuildEngineResult result = await taskHost.InternalBuildProjects(projectNames, targetList, projectProperties, undefinePropertiesPerProject, toolsVersions, true /* ask that target outputs are returned in the buildengineresult */, skipNonexistentTargets); bool currentTargetResult = result.Result; IList <IDictionary <string, ITaskItem[]> > targetOutputsPerProject = result.TargetOutputsPerProject; success = success && currentTargetResult; // If the engine was able to satisfy the build request if (currentTargetResult) { for (int i = 0; i < projects.Length; i++) { IEnumerable <string> nonNullTargetList = targetList ?? targetOutputsPerProject[i].Keys; foreach (string targetName in nonNullTargetList) { if (targetOutputsPerProject[i].ContainsKey(targetName)) { ITaskItem[] outputItemsFromTarget = targetOutputsPerProject[i][targetName]; foreach (ITaskItem outputItemFromTarget in outputItemsFromTarget) { // No need to rebase if the calling project is the same as the callee project // (project == null). Also no point in trying to copy item metadata either, // because no items were passed into the Projects parameter! if (projects[i] != null) { // Rebase the output item paths if necessary. No need to rebase if the calling // project is the same as the callee project (project == null). if (rebaseOutputs) { try { outputItemFromTarget.ItemSpec = Path.Combine(projectDirectory[i], outputItemFromTarget.ItemSpec); } catch (ArgumentException e) { log.LogWarningWithCodeFromResources(null, projects[i].ItemSpec, 0, 0, 0, 0, "MSBuild.CannotRebaseOutputItemPath", outputItemFromTarget.ItemSpec, e.Message); } } // Copy the custom item metadata from the "Projects" items to these // output items. projects[i].CopyMetadataTo(outputItemFromTarget); // Set a metadata on the output items called "MSBuildProjectFile" which tells you which project file produced this item. if (String.IsNullOrEmpty(outputItemFromTarget.GetMetadata(ItemMetadataNames.msbuildSourceProjectFile))) { outputItemFromTarget.SetMetadata(ItemMetadataNames.msbuildSourceProjectFile, projects[i].GetMetadata(FileUtilities.ItemSpecModifiers.FullPath)); } } // Set a metadata on the output items called "MSBuildTargetName" which tells you which target produced this item. if (String.IsNullOrEmpty(outputItemFromTarget.GetMetadata(ItemMetadataNames.msbuildSourceTargetName))) { outputItemFromTarget.SetMetadata(ItemMetadataNames.msbuildSourceTargetName, targetName); } } targetOutputs.AddRange(outputItemsFromTarget); } } } } } return(success); }
/// <summary> /// Instructs the MSBuild engine to build one or more project files whose locations are specified by the /// <see cref="Projects"/> property. /// </summary> /// <returns>true if all projects build successfully; false if any project fails</returns> public async Task <bool> ExecuteInternal() { // If no projects were passed in, just return success. if ((Projects == null) || (Projects.Length == 0)) { return(true); } // We have been asked to unescape all escaped characters before processing if (TargetAndPropertyListSeparators != null && TargetAndPropertyListSeparators.Length > 0) { ExpandAllTargetsAndProperties(); } // Parse the global properties into a hashtable. // The behavior of parsing global properties to define and undefine (below) combined with the behavior in Microsoft.Common.CurrentVersion.targets should match the logic in ProjectGraph. if (!PropertyParser.GetTableWithEscaping(Log, ResourceUtilities.GetResourceString("General.GlobalProperties"), "Properties", Properties, out Dictionary <string, string> propertiesTable)) { return(false); } // Parse out the properties to undefine, if any. string[] undefinePropertiesArray = null; if (!String.IsNullOrEmpty(RemoveProperties)) { Log.LogMessageFromResources(MessageImportance.Low, "General.UndefineProperties"); undefinePropertiesArray = RemoveProperties.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); foreach (string property in undefinePropertiesArray) { Log.LogMessageFromText(String.Format(CultureInfo.InvariantCulture, " {0}", property), MessageImportance.Low); } } bool isRunningMultipleNodes = BuildEngine2.IsRunningMultipleNodes; // If we are in single proc mode and stopOnFirstFailure is true, we cannot build in parallel because // building in parallel sends all of the projects to the engine at once preventing us from not sending // any more projects after the first failure. Therefore, to preserve compatibility with whidbey if we are in this situation disable buildInParallel. if (!isRunningMultipleNodes && StopOnFirstFailure && BuildInParallel) { BuildInParallel = false; Log.LogMessageFromResources(MessageImportance.Low, "MSBuild.NotBuildingInParallel"); } // When the condition below is met, provide an information message indicating stopOnFirstFailure // will have no effect. The reason there will be no effect is, when buildInParallel is true // All project files will be submitted to the engine all at once, this mean there is no stopping for failures between projects. // When RunEachTargetSeparately is false, all targets will be submitted to the engine at once, this means there is no way to stop between target failures. // therefore the first failure seen will be the only failure seen. if (isRunningMultipleNodes && BuildInParallel && StopOnFirstFailure && !RunEachTargetSeparately) { Log.LogMessageFromResources(MessageImportance.Low, "MSBuild.NoStopOnFirstFailure"); } // This is a list of string[]. That is, each element in the list is a string[]. Each // string[] represents a set of target names to build. Depending on the value // of the RunEachTargetSeparately parameter, we each just call the engine to run all // the targets together, or we call the engine separately for each target. List <string[]> targetLists = CreateTargetLists(Targets, RunEachTargetSeparately); bool success = true; ITaskItem[] singleProject = null; bool[] skipProjects = null; if (BuildInParallel) { skipProjects = new bool[Projects.Length]; for (int i = 0; i < skipProjects.Length; i++) { skipProjects[i] = true; } } else { singleProject = new ITaskItem[1]; } // Read in each project file. If there are any errors opening the file or parsing the XML, // raise an event and return False. If any one of the projects fails to build, return False, // otherwise return True. If parallel build is requested we first check for file existence so // that we don't pass a non-existent file to IBuildEngine causing an exception for (int i = 0; i < Projects.Length; i++) { ITaskItem project = Projects[i]; string projectPath = FileUtilities.AttemptToShortenPath(project.ItemSpec); if (StopOnFirstFailure && !success) { // Inform the user that we skipped the remaining projects because StopOnFirstFailure=true. Log.LogMessageFromResources(MessageImportance.Low, "MSBuild.SkippingRemainingProjects"); // We have encountered a failure. Caller has requested that we not // continue with remaining projects. break; } if (FileSystems.Default.FileExists(projectPath) || (_skipNonexistentProjects == SkipNonexistentProjectsBehavior.Build)) { if (FileUtilities.IsVCProjFilename(projectPath)) { Log.LogErrorWithCodeFromResources("MSBuild.ProjectUpgradeNeededToVcxProj", project.ItemSpec); success = false; continue; } // If we are building in parallel we want to only make one call to // ExecuteTargets once we verified that all projects exist if (!BuildInParallel) { singleProject[0] = project; bool executeResult = await ExecuteTargets( singleProject, propertiesTable, undefinePropertiesArray, targetLists, StopOnFirstFailure, RebaseOutputs, BuildEngine3, Log, _targetOutputs, UnloadProjectsOnCompletion, ToolsVersion, SkipNonexistentTargets ); if (!executeResult) { success = false; } } else { skipProjects[i] = false; } } else { if (_skipNonexistentProjects == SkipNonexistentProjectsBehavior.Skip) { Log.LogMessageFromResources(MessageImportance.High, "MSBuild.ProjectFileNotFoundMessage", project.ItemSpec); } else { ErrorUtilities.VerifyThrow(_skipNonexistentProjects == SkipNonexistentProjectsBehavior.Error, "skipNonexistentProjects has unexpected value {0}", _skipNonexistentProjects); Log.LogErrorWithCodeFromResources("MSBuild.ProjectFileNotFound", project.ItemSpec); success = false; } } } // We need to build all the projects that were not skipped if (BuildInParallel) { success = await BuildProjectsInParallel(propertiesTable, undefinePropertiesArray, targetLists, success, skipProjects); } return(success); }
/// <summary> /// Constructor gets the data for the filename. /// On Win32 it uses native means. Otherwise, /// uses standard .NET FileInfo/DirInfo /// </summary> public FileDirInfo(string filename) { Exists = false; // If file/directory does not exist, return 12 midnight 1/1/1601. LastWriteTimeUtc = new DateTime(1601, 1, 1); _filename = FileUtilities.AttemptToShortenPath(filename); // This is no-op unless the path actually is too long int oldMode = 0; if (NativeMethodsShared.IsWindows) { // THIS COPIED FROM THE BCL: // // For floppy drives, normally the OS will pop up a dialog saying // there is no disk in drive A:, please insert one. We don't want that. // SetErrorMode will let us disable this, but we should set the error // mode back, since this may have wide-ranging effects. oldMode = NativeMethodsShared.SetErrorMode(1 /* ErrorModes.SEM_FAILCRITICALERRORS */); } try { if (NativeMethodsShared.IsWindows) { var data = new NativeMethodsShared.WIN32_FILE_ATTRIBUTE_DATA(); bool success = NativeMethodsShared.GetFileAttributesEx(_filename, 0, ref data); if (!success) { int error = Marshal.GetLastWin32Error(); // File not found is the most common case, for example we're copying // somewhere without a file yet. Don't do something like FileInfo.Exists to // get a nice error, or we're doing IO again! Don't even format our own string: // that turns out to be unacceptably expensive here as well. Set a flag for this particular case. // // Also, when not under debugger (!) it will give error == 3 for path too long. Make that consistently throw instead. if ((error == 2 /* ERROR_FILE_NOT_FOUND */ || error == 3 /* ERROR_PATH_NOT_FOUND */) && _filename.Length <= NativeMethodsShared.MAX_PATH) { Exists = false; return; } // Throw nice message as far as we can. At this point IO is OK. Length = new FileInfo(_filename).Length; // Otherwise this will give at least something NativeMethodsShared.ThrowExceptionForErrorCode(error); ErrorUtilities.ThrowInternalErrorUnreachable(); } Exists = true; IsDirectory = (data.fileAttributes & NativeMethodsShared.FILE_ATTRIBUTE_DIRECTORY) != 0; IsReadOnly = !IsDirectory && (data.fileAttributes & NativeMethodsShared.FILE_ATTRIBUTE_READONLY) != 0; LastWriteTimeUtc = DateTime.FromFileTimeUtc(((long)data.ftLastWriteTimeHigh << 0x20) | data.ftLastWriteTimeLow); Length = IsDirectory ? 0 : (((long)data.fileSizeHigh << 0x20) | data.fileSizeLow); } else { // Check if we have a directory IsDirectory = Directory.Exists(_filename); Exists = IsDirectory; // If not exists, see if this is a file if (!Exists) { Exists = File.Exists(_filename); } if (IsDirectory) { // Use DirectoryInfo to get the last write date var directoryInfo = new DirectoryInfo(_filename); IsReadOnly = false; LastWriteTimeUtc = directoryInfo.LastWriteTimeUtc; } else if (Exists) { // Use FileInfo to get readonly and last write date var fileInfo = new FileInfo(_filename); IsReadOnly = fileInfo.IsReadOnly; LastWriteTimeUtc = fileInfo.LastWriteTimeUtc; Length = fileInfo.Length; } } } catch (Exception ex) { // Save the exception thrown and assume the file does not exist _exceptionThrown = ex; Exists = false; } finally { // Reset the error mode on Windows if (NativeMethodsShared.IsWindows) { NativeMethodsShared.SetErrorMode(oldMode); } } }
/// <returns>True if the operation was successful</returns> internal static bool ExecuteTargets ( List <ITaskItem> projects, Dictionary <string, string> propertiesTable, string[] undefineProperties, List <string[]> targetLists, bool stopOnFirstFailure, bool rebaseOutputs, IBuildEngine3 buildEngine, TaskLoggingHelper log, List <ITaskItem> targetOutputs, bool unloadProjectsOnCompletion, string toolsVersion ) { bool success = true; // We don't log a message about the project and targets we're going to // build, because it'll all be in the immediately subsequent ProjectStarted event. var projectDirectory = new string[projects.Count]; var projectNames = new string[projects.Count]; var toolsVersions = new string[projects.Count]; var projectProperties = new Dictionary <string, string> [projects.Count]; var undefinePropertiesPerProject = new IList <string> [projects.Count]; for (int i = 0; i < projectNames.Length; i++) { projectNames[i] = null; projectProperties[i] = propertiesTable; if (projects[i] != null) { // Retrieve projectDirectory only the first time. It never changes anyway. string projectPath = FileUtilities.AttemptToShortenPath(projects[i].ItemSpec); projectDirectory[i] = Path.GetDirectoryName(projectPath); projectNames[i] = projects[i].ItemSpec; toolsVersions[i] = toolsVersion; // If the user specified a different set of global properties for this project, then // parse the string containing the properties if (!String.IsNullOrEmpty(projects[i].GetMetadata("Properties"))) { if (!PropertyParser.GetTableWithEscaping (log, ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.OverridingProperties", projectNames[i]), "Properties", projects[i].GetMetadata("Properties").Split(MSBuildConstants.SemicolonChar), out Dictionary <string, string> preProjectPropertiesTable) ) { return(false); } projectProperties[i] = preProjectPropertiesTable; } if (undefineProperties != null) { undefinePropertiesPerProject[i] = new List <string>(undefineProperties); } // If the user wanted to undefine specific global properties for this project, parse // that string and remove them now. string projectUndefineProperties = projects[i].GetMetadata("UndefineProperties"); if (!String.IsNullOrEmpty(projectUndefineProperties)) { string[] propertiesToUndefine = projectUndefineProperties.Split(MSBuildConstants.SemicolonChar); if (undefinePropertiesPerProject[i] == null) { undefinePropertiesPerProject[i] = new List <string>(propertiesToUndefine.Length); } if (log != null && propertiesToUndefine.Length > 0) { log.LogMessageFromResources(MessageImportance.Low, "General.ProjectUndefineProperties", projectNames[i]); foreach (string property in propertiesToUndefine) { undefinePropertiesPerProject[i].Add(property); log.LogMessageFromText($" {property}", MessageImportance.Low); } } } // If the user specified a different set of global properties for this project, then // parse the string containing the properties if (!String.IsNullOrEmpty(projects[i].GetMetadata("AdditionalProperties"))) { if (!PropertyParser.GetTableWithEscaping (log, ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("General.AdditionalProperties", projectNames[i]), "AdditionalProperties", projects[i].GetMetadata("AdditionalProperties").Split(MSBuildConstants.SemicolonChar), out Dictionary <string, string> additionalProjectPropertiesTable) ) { return(false); } var combinedTable = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); // First copy in the properties from the global table that not in the additional properties table if (projectProperties[i] != null) { foreach (KeyValuePair <string, string> entry in projectProperties[i]) { if (!additionalProjectPropertiesTable.ContainsKey(entry.Key)) { combinedTable.Add(entry.Key, entry.Value); } } } // Add all the additional properties foreach (KeyValuePair <string, string> entry in additionalProjectPropertiesTable) { combinedTable.Add(entry.Key, entry.Value); } projectProperties[i] = combinedTable; } // If the user specified a different toolsVersion for this project - then override the setting if (!String.IsNullOrEmpty(projects[i].GetMetadata("ToolsVersion"))) { toolsVersions[i] = projects[i].GetMetadata("ToolsVersion"); } } } foreach (string[] targetList in targetLists) { if (stopOnFirstFailure && !success) { // Inform the user that we skipped the remaining targets StopOnFirstFailure=true. log.LogMessageFromResources(MessageImportance.Low, "MSBuild.SkippingRemainingTargets"); // We have encountered a failure. Caller has requested that we not // continue with remaining targets. break; } // Send the project off to the build engine. By passing in null to the // first param, we are indicating that the project to build is the same // as the *calling* project file. BuildEngineResult result = buildEngine.BuildProjectFilesInParallel(projectNames, targetList, projectProperties, undefinePropertiesPerProject, toolsVersions, true /* ask that target outputs are returned in the buildengineresult */); bool currentTargetResult = result.Result; IList <IDictionary <string, ITaskItem[]> > targetOutputsPerProject = result.TargetOutputsPerProject; success = success && currentTargetResult; // If the engine was able to satisfy the build request if (currentTargetResult) { for (int i = 0; i < projects.Count; i++) { IEnumerable <string> nonNullTargetList = targetList ?? targetOutputsPerProject[i].Keys; foreach (string targetName in nonNullTargetList) { if (targetOutputsPerProject[i].TryGetValue(targetName, out ITaskItem[] outputItemsFromTarget))