/// <summary> /// Attempts to read project information for the given file. /// </summary> /// <param name="File">The project file to read</param> /// <param name="Properties">Initial set of property values</param> /// <param name="OutProjectInfo">If successful, the parsed project info</param> /// <returns>True if the project was read successfully, false otherwise</returns> public static bool TryRead(FileReference File, Dictionary <string, string> Properties, out CsProjectInfo OutProjectInfo) { // Read the project file XmlDocument Document = new XmlDocument(); Document.Load(File.FullName); // Check the root element is the right type // HashSet<FileReference> ProjectBuildProducts = new HashSet<FileReference>(); if (Document.DocumentElement.Name != "Project") { OutProjectInfo = null; return(false); } // Parse the basic structure of the document, updating properties and recursing into other referenced projects as we go CsProjectInfo ProjectInfo = new CsProjectInfo(Properties); foreach (XmlElement Element in Document.DocumentElement.ChildNodes.OfType <XmlElement>()) { switch (Element.Name) { case "PropertyGroup": if (EvaluateCondition(Element, ProjectInfo.Properties)) { ParsePropertyGroup(Element, ProjectInfo.Properties); } break; case "ItemGroup": if (EvaluateCondition(Element, ProjectInfo.Properties)) { ParseItemGroup(File.Directory, Element, ProjectInfo); } break; } } // Return the complete project OutProjectInfo = ProjectInfo; return(true); }
/// <summary> /// Read a project file, plus all the project files it references. /// </summary> /// <param name="File">Project file to read</param> /// <param name="InitialProperties">Mapping of property name to value for the initial project</param> /// <param name="FileToProjectInfo"></param> /// <returns>True if the projects were read correctly, false (and prints an error to the log) if not</returns> static void ReadProjectsRecursively(FileReference File, Dictionary <string, string> InitialProperties, Dictionary <FileReference, CsProjectInfo> FileToProjectInfo) { // Early out if we've already read this project if (!FileToProjectInfo.ContainsKey(File)) { // Try to read this project CsProjectInfo ProjectInfo; if (!CsProjectInfo.TryRead(File, InitialProperties, out ProjectInfo)) { throw new AutomationException("Couldn't read project '{0}'", File.FullName); } // Add it to the project lookup, and try to read all the projects it references FileToProjectInfo.Add(File, ProjectInfo); foreach (FileReference ProjectReference in ProjectInfo.ProjectReferences.Keys) { ReadProjectsRecursively(ProjectReference, InitialProperties, FileToProjectInfo); } } }
/// <summary> /// Read a project file, plus all the project files it references. /// </summary> /// <param name="File">Project file to read</param> /// <param name="InitialProperties">Mapping of property name to value for the initial project</param> /// <param name="FileToProjectInfo"></param> /// <returns>True if the projects were read correctly, false (and prints an error to the log) if not</returns> static bool ReadProjectsRecursively(FileReference File, Dictionary <string, string> InitialProperties, Dictionary <FileReference, CsProjectInfo> FileToProjectInfo) { // Early out if we've already read this project, return success if (FileToProjectInfo.ContainsKey(File)) { return(true); } // Try to read this project CsProjectInfo ProjectInfo; if (!CsProjectInfo.TryRead(File, InitialProperties, out ProjectInfo)) { CommandUtils.LogError("Couldn't read project '{0}'", File.FullName); return(false); } // Add it to the project lookup, and try to read all the projects it references FileToProjectInfo.Add(File, ProjectInfo); return(ProjectInfo.ProjectReferences.Keys.All(x => ReadProjectsRecursively(x, InitialProperties, FileToProjectInfo))); }
/// <summary> /// Find all the build products created by compiling the given project file /// </summary> /// <param name="ProjectFiles">Initial project file to read. All referenced projects will also be read.</param> /// <param name="InitialProperties">Mapping of property name to value</param> /// <param name="OutBuildProducts">Receives a set of build products on success</param> /// <param name="OutReferences">Receives a set of non-private references on success</param> static void FindBuildProductsAndReferences(HashSet <FileReference> ProjectFiles, Dictionary <string, string> InitialProperties, out HashSet <FileReference> OutBuildProducts, out HashSet <FileReference> OutReferences) { // Find all the build products and references OutBuildProducts = new HashSet <FileReference>(); OutReferences = new HashSet <FileReference>(); // Read all the project information into a dictionary Dictionary <FileReference, CsProjectInfo> FileToProjectInfo = new Dictionary <FileReference, CsProjectInfo>(); foreach (FileReference ProjectFile in ProjectFiles) { // Read all the projects ReadProjectsRecursively(ProjectFile, InitialProperties, FileToProjectInfo); // Find all the outputs for each project foreach (KeyValuePair <FileReference, CsProjectInfo> Pair in FileToProjectInfo) { CsProjectInfo ProjectInfo = Pair.Value; // Add all the build projects from this project DirectoryReference OutputDir = ProjectInfo.GetOutputDir(Pair.Key.Directory); ProjectInfo.FindBuildProducts(OutputDir, OutBuildProducts, FileToProjectInfo); // Add any files which are only referenced foreach (KeyValuePair <FileReference, bool> Reference in ProjectInfo.References) { if (!Reference.Value) { CsProjectInfo.AddReferencedAssemblyAndSupportFiles(Reference.Key, OutReferences); } } } } OutBuildProducts.RemoveWhere(x => !FileReference.Exists(x)); OutReferences.RemoveWhere(x => !FileReference.Exists(x)); }
/// <summary> /// Parses an 'ItemGroup' element. /// </summary> /// <param name="BaseDirectory">Base directory to resolve relative paths against</param> /// <param name="ParentElement">The parent 'ItemGroup' element</param> /// <param name="ProjectInfo">Project info object to be updated</param> static void ParseItemGroup(DirectoryReference BaseDirectory, XmlElement ParentElement, CsProjectInfo ProjectInfo) { // Parse any external assembly references foreach (XmlElement ItemElement in ParentElement.ChildNodes.OfType <XmlElement>()) { switch (ItemElement.Name) { case "Reference": // Reference to an external assembly if (EvaluateCondition(ItemElement, ProjectInfo.Properties)) { ParseReference(BaseDirectory, ItemElement, ProjectInfo.References); } break; case "ProjectReference": // Reference to another project if (EvaluateCondition(ItemElement, ProjectInfo.Properties)) { ParseProjectReference(BaseDirectory, ItemElement, ProjectInfo.ProjectReferences); } break; } } }
/// <summary> /// Find all the build products created by compiling the given project file /// </summary> /// <param name="ProjectFiles">Initial project file to read. All referenced projects will also be read.</param> /// <param name="InitialProperties">Mapping of property name to value</param> /// <param name="OutBuildProducts">Receives a set of build products on success</param> /// <param name="OutReferences">Receives a set of non-private references on success</param> /// <returns>True if the build products were found, false otherwise.</returns> static bool FindBuildProducts(HashSet <FileReference> ProjectFiles, Dictionary <string, string> InitialProperties, out HashSet <FileReference> OutBuildProducts, out HashSet <FileReference> OutReferences) { // Read all the project information into a dictionary Dictionary <FileReference, CsProjectInfo> FileToProjectInfo = new Dictionary <FileReference, CsProjectInfo>(); foreach (FileReference ProjectFile in ProjectFiles) { if (!ReadProjectsRecursively(ProjectFile, InitialProperties, FileToProjectInfo)) { OutBuildProducts = null; OutReferences = null; return(false); } } // Find all the build products and references HashSet <FileReference> BuildProducts = new HashSet <FileReference>(); HashSet <FileReference> References = new HashSet <FileReference>(); foreach (KeyValuePair <FileReference, CsProjectInfo> Pair in FileToProjectInfo) { CsProjectInfo ProjectInfo = Pair.Value; // Add the standard build products DirectoryReference OutputDir = ProjectInfo.GetOutputDir(Pair.Key.Directory); ProjectInfo.AddBuildProducts(OutputDir, BuildProducts); // Add the referenced assemblies foreach (KeyValuePair <FileReference, bool> Reference in ProjectInfo.References) { FileReference OtherAssembly = Reference.Key; if (Reference.Value) { // Add reference from the output dir FileReference OutputFile = FileReference.Combine(OutputDir, OtherAssembly.GetFileName()); BuildProducts.Add(OutputFile); FileReference OutputSymbolFile = OutputFile.ChangeExtension(".pdb"); if (FileReference.Exists(OutputSymbolFile)) { BuildProducts.Add(OutputSymbolFile); } } else { // Add reference directly References.Add(OtherAssembly); FileReference SymbolFile = OtherAssembly.ChangeExtension(".pdb"); if (FileReference.Exists(SymbolFile)) { References.Add(SymbolFile); } } } // Add build products from all the referenced projects. MSBuild only copy the directly referenced build products, not recursive references or other assemblies. foreach (CsProjectInfo OtherProjectInfo in ProjectInfo.ProjectReferences.Where(x => x.Value).Select(x => FileToProjectInfo[x.Key])) { OtherProjectInfo.AddBuildProducts(OutputDir, BuildProducts); } } // Update the output set OutBuildProducts = BuildProducts; OutReferences = References; return(true); }
/// <summary> /// Parses an 'ItemGroup' element. /// </summary> /// <param name="BaseDirectory">Base directory to resolve relative paths against</param> /// <param name="ParentElement">The parent 'ItemGroup' element</param> /// <param name="ProjectInfo">Project info object to be updated</param> static void ParseItemGroup(DirectoryReference BaseDirectory, XmlElement ParentElement, CsProjectInfo ProjectInfo) { // Parse any external assembly references foreach(XmlElement ItemElement in ParentElement.ChildNodes.OfType<XmlElement>()) { switch(ItemElement.Name) { case "Reference": // Reference to an external assembly if(EvaluateCondition(ItemElement, ProjectInfo.Properties)) { ParseReference(BaseDirectory, ItemElement, ProjectInfo.References); } break; case "ProjectReference": // Reference to another project if(EvaluateCondition(ItemElement, ProjectInfo.Properties)) { ParseProjectReference(BaseDirectory, ItemElement, ProjectInfo.ProjectReferences); } break; } } }
/// <summary> /// Attempts to read project information for the given file. /// </summary> /// <param name="File">The project file to read</param> /// <param name="Properties">Initial set of property values</param> /// <param name="OutProjectInfo">If successful, the parsed project info</param> /// <returns>True if the project was read successfully, false otherwise</returns> public static bool TryRead(FileReference File, Dictionary<string, string> Properties, out CsProjectInfo OutProjectInfo) { // Read the project file XmlDocument Document = new XmlDocument(); Document.Load(File.FullName); // Check the root element is the right type // HashSet<FileReference> ProjectBuildProducts = new HashSet<FileReference>(); if(Document.DocumentElement.Name != "Project") { OutProjectInfo = null; return false; } // Parse the basic structure of the document, updating properties and recursing into other referenced projects as we go CsProjectInfo ProjectInfo = new CsProjectInfo(Properties); foreach(XmlElement Element in Document.DocumentElement.ChildNodes.OfType<XmlElement>()) { switch(Element.Name) { case "PropertyGroup": if(EvaluateCondition(Element, ProjectInfo.Properties)) { ParsePropertyGroup(Element, ProjectInfo.Properties); } break; case "ItemGroup": if(EvaluateCondition(Element, ProjectInfo.Properties)) { ParseItemGroup(File.Directory, Element, ProjectInfo); } break; } } // Return the complete project OutProjectInfo = ProjectInfo; return true; }