/// <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, File); // Parse elements in the root node ParseNode(Document.DocumentElement, ProjectInfo); // Return the complete project OutProjectInfo = ProjectInfo; return(true); }
/// <summary> /// Parses supported elements from the children of the provided note. May recurse /// for conditional elements. /// </summary> /// <param name="Node"></param> /// <param name="ProjectInfo"></param> static void ParseNode(XmlNode Node, CsProjectInfo ProjectInfo) { foreach (XmlElement Element in Node.ChildNodes.OfType <XmlElement>()) { switch (Element.Name) { case "PropertyGroup": if (EvaluateCondition(Element, ProjectInfo)) { ParsePropertyGroup(Element, ProjectInfo); } break; case "ItemGroup": if (EvaluateCondition(Element, ProjectInfo)) { ParseItemGroup(ProjectInfo.ProjectPath.Directory, Element, ProjectInfo); } break; case "Choose": case "When": if (EvaluateCondition(Element, ProjectInfo)) { ParseNode(Element, ProjectInfo); } break; } } }
/// <summary> /// Adds aall input/output properties of a CSProject to a hash collection /// </summary> /// <param name="Hasher"></param> /// <param name="Project"></param> /// <returns></returns> public static bool AddCsProjectInfo(this HashCollection Hasher, CsProjectInfo Project, HashCollection.HashType HashType) { // Get the output assembly and pdb file FileReference OutputFile; if (!Project.TryGetOutputFile(out OutputFile)) { throw new Exception(String.Format("Unable to get output file for {0}", Project.ProjectPath)); } FileReference DebugFile = OutputFile.ChangeExtension("pdb"); // build a list of all input and output files from this module List <FileReference> DependentFiles = new List <FileReference> { Project.ProjectPath, OutputFile, DebugFile }; DependentFiles.AddRange(Project.CompileReferences); if (!Hasher.AddFiles(DependentFiles, HashType)) { return(false); } return(true); }
/// <summary> /// Parses a 'PropertyGroup' element. /// </summary> /// <param name="ParentElement">The parent 'PropertyGroup' element</param> /// <param name="Properties">Dictionary mapping property names to values</param> static void ParsePropertyGroup(XmlElement ParentElement, CsProjectInfo ProjectInfo) { // We need to know the overridden output type and output path for the selected configuration. foreach (XmlElement Element in ParentElement.ChildNodes.OfType <XmlElement>()) { if (EvaluateCondition(Element, ProjectInfo)) { ProjectInfo.Properties[Element.Name] = ExpandProperties(Element.InnerText, ProjectInfo.Properties); } } }
/// <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> /// Adds aall input/output properties of a CSProject to a hash collection /// </summary> /// <param name="Hasher"></param> /// <param name="Project"></param> /// <returns></returns> public static bool AddCsProjectInfo(this HashCollection Hasher, CsProjectInfo Project, HashCollection.HashType HashType) { // Get the output assembly and pdb file DirectoryReference ProjectDirectory = Project.ProjectPath.Directory; DirectoryReference OutputDir = Project.GetOutputDir(ProjectDirectory); FileReference OutputFile = FileReference.Combine(OutputDir, Project.GetAssemblyName() + ".dll"); FileReference DebugFile = OutputFile.ChangeExtension("pdb"); // build a list of all input and output files from this module List <FileReference> DependentFiles = new List <FileReference> { Project.ProjectPath, OutputFile, DebugFile }; DependentFiles.AddRange(Project.CompileReferences); if (!Hasher.AddFiles(DependentFiles, HashType)) { return(false); } 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; case "Content": case "None": // Reference to another project if (EvaluateCondition(ItemElement, ProjectInfo.Properties)) { ParseContent(BaseDirectory, ItemElement, ProjectInfo.ContentReferences); } break; } } }
/// <summary> /// Evaluate whether the optional MSBuild condition on an XML element evaluates to true. Currently only supports 'ABC' == 'DEF' style expressions, but can be expanded as needed. /// </summary> /// <param name="Element">The XML element to check</param> /// <param name="Properties">Dictionary mapping from property names to values.</param> /// <returns></returns> static bool EvaluateCondition(XmlElement Element, CsProjectInfo ProjectInfo) { // Read the condition attribute. If it's not present, assume it evaluates to true. string Condition = Element.GetAttribute("Condition"); if (String.IsNullOrEmpty(Condition)) { return(true); } // Expand all the properties Condition = ExpandProperties(Condition, ProjectInfo.Properties); // Parse literal true/false values bool OutResult; if (bool.TryParse(Condition, out OutResult)) { return(OutResult); } // Tokenize the condition string[] Tokens = Tokenize(Condition); char[] TokenQuotes = new[] { '\'', '(', ')', '{', '}', '[', ']' }; // Try to evaluate it. We only support a very limited class of condition expressions at the moment, but it's enough to parse standard projects bool bResult; // Handle Exists('Platform\Windows\Gauntlet.TargetDeviceWindows.cs') if (Tokens[0] == "Exists") { // remove all quotes, apostrophes etc that are either tokens or wrap tokens (The Tokenize() function is a bit suspect). string[] Arguments = Tokens.Select(S => S.Trim(TokenQuotes)).Where(S => S.Length > 0).ToArray(); if (Tokens.Length > 1) { FileSystemReference Dependency = DirectoryReference.Combine(ProjectInfo.ProjectPath.Directory, Arguments[1]); if (File.Exists(Dependency.FullName) || Directory.Exists(Dependency.FullName)) { return(true); } return(false); } } if (Tokens.Length == 3 && Tokens[0].StartsWith("'") && Tokens[1] == "==" && Tokens[2].StartsWith("'")) { bResult = String.Compare(Tokens[0], Tokens[2], StringComparison.InvariantCultureIgnoreCase) == 0; } else if (Tokens.Length == 3 && Tokens[0].StartsWith("'") && Tokens[1] == "!=" && Tokens[2].StartsWith("'")) { bResult = String.Compare(Tokens[0], Tokens[2], StringComparison.InvariantCultureIgnoreCase) != 0; } else { string Msg = string.Format("Couldn't parse condition {0} in project file {1}", Element.ToString(), ProjectInfo.ProjectPath); throw new Exception(Msg); } return(bResult); }