Last active
June 3, 2023 12:49
-
-
Save ifree/16ea5d9f984843f8774deea76a85b007 to your computer and use it in GitHub Desktop.
When I realized there is a thing called msbuild, I orz...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| static class UtilityExtension | |
| { | |
| //xml utility | |
| public static XAttribute AttributeAnyNS<T>(this T source, string localName) | |
| where T : XElement | |
| { | |
| return source.Attributes().SingleOrDefault(e => e.Name.LocalName == localName); | |
| } | |
| public static IEnumerable<XElement> ElementsAnyNS<T>(this T source, string localName) | |
| where T : XContainer | |
| { | |
| return source.Elements().Where(e => e.Name.LocalName == localName); | |
| } | |
| public static IEnumerable<XElement> ElementsAnyNS<T>(this IEnumerable<T> source, string localName) | |
| where T : XContainer | |
| { | |
| return source.Elements().Where(e => e.Name.LocalName == localName); | |
| } | |
| public static XElement ElementAnyNS<T>(this T source, string localName) | |
| where T : XContainer | |
| { | |
| return source.Elements().Where(e => e.Name.LocalName == localName).FirstOrDefault(); | |
| } | |
| public static void SingleNodeDispatch<T>(this T source, string localName, Action<XElement> action) | |
| where T : XContainer | |
| { | |
| var elements = source.ElementsAnyNS(localName); | |
| foreach (var elem in elements) | |
| action(elem); | |
| } | |
| public static void MultiNodeDispatch<T>(this T source, List<Tuple<string, Action<XElement>>> preds) | |
| where T : XContainer | |
| { | |
| //yes, brute force cross product... | |
| var elements = source.Elements(); | |
| foreach (var elem in elements) | |
| { | |
| preds.Where(p => p.Item1 == elem.Name.LocalName).ForEach(pred => pred.Item2(elem)); | |
| } | |
| } | |
| //other utility | |
| public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) | |
| { | |
| foreach (var item in source) | |
| action(item); | |
| } | |
| public static TV GetValue<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue = default(TV)) | |
| { | |
| TV value; | |
| return dict.TryGetValue(key, out value) ? value : defaultValue; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| namespace orz{ | |
| public class ProjectConfiguration | |
| { | |
| public ProjectConfiguration(string name, string platform) | |
| { | |
| properties = new Dictionary<string, string> { | |
| {"Name", name }, | |
| {"Platform", platform } | |
| }; | |
| exclude_items = new List<Tuple<string, string>>(); | |
| compile_options = new Dictionary<string, string>(); | |
| link_options = new Dictionary<string, string>(); | |
| lib_options = new Dictionary<string, string>(); | |
| res_options = new Dictionary<string, string>(); | |
| } | |
| public string this[string key]{ | |
| get | |
| { | |
| return get_property(key); | |
| } | |
| } | |
| public string get_property(string key) | |
| { | |
| return properties.GetValue(key); | |
| } | |
| public void set_property(string key, string value) | |
| { | |
| properties[key] = value; | |
| } | |
| public void add_exclude(string type, string path) | |
| { | |
| exclude_items.Add(new Tuple<string, string>(type, path)); | |
| } | |
| public void add_excludes(string type, IEnumerable<string> paths) | |
| { | |
| exclude_items.AddRange(paths.Select(p => new Tuple<string, string>(type, p))); | |
| } | |
| public void add_compile_option(string key, string value) | |
| { | |
| compile_options.Add(key, value); | |
| } | |
| public void add_link_option(string key, string value) | |
| { | |
| link_options.Add(key, value); | |
| } | |
| public void add_lib_option(string key, string value) | |
| { | |
| lib_options.Add(key, value); | |
| } | |
| public void add_res_option(string key, string value) | |
| { | |
| res_options.Add(key, value); | |
| } | |
| public IEnumerable<string> get_excludes(string type) | |
| { | |
| return exclude_items.Where(e => e.Item1 == type).Select(e => e.Item2); | |
| } | |
| public string get_compile_option(string key) | |
| { | |
| return compile_options?[key]; | |
| } | |
| public string get_link_option(string key) | |
| { | |
| return link_options?[key]; | |
| } | |
| public string get_lib_option(string key) | |
| { | |
| return lib_options?[key]; | |
| } | |
| public string get_res_option(string key) | |
| { | |
| return res_options?[key]; | |
| } | |
| private Dictionary<string, string> properties; | |
| // type_name, path | |
| private List<Tuple<string, string>> exclude_items; | |
| private Dictionary<string, string> compile_options; | |
| private Dictionary<string, string> lib_options; | |
| private Dictionary<string, string> link_options; | |
| private Dictionary<string, string> res_options; | |
| } | |
| public class ProjectInfo | |
| { | |
| public ProjectInfo(string name, string guid) | |
| { | |
| this.Name = name; | |
| this.GUID = guid; | |
| source_items = new List<Tuple<string, string>>(); | |
| configurations = new Dictionary<string, ProjectConfiguration>(); | |
| } | |
| public string Name | |
| { | |
| get; | |
| private set; | |
| } | |
| public string GUID | |
| { | |
| get; | |
| private set; | |
| } | |
| public ProjectConfiguration get_config(string name) | |
| { | |
| return configurations.GetValue(name); | |
| } | |
| public IEnumerable<string> get_config_keys() | |
| { | |
| return configurations.Keys; | |
| } | |
| public void add_config(string name, ProjectConfiguration config) | |
| { | |
| configurations.Add(name, config); | |
| } | |
| public void add_source(string type, string path) | |
| { | |
| source_items.Add(new Tuple<string, string>(type, path)); | |
| } | |
| public void add_sources(string type, IEnumerable<string> paths) | |
| { | |
| source_items.AddRange(paths.Select(p => new Tuple<string, string>(type, p))); | |
| } | |
| //avoid modification | |
| public void visit_source(Action<Tuple<string, string>> visitor) | |
| { | |
| source_items.ForEach(visitor); | |
| } | |
| public IEnumerable<string> get_source(string type) | |
| { | |
| //yes, performance issue | |
| return source_items.Where(src => src.Item1 == type).Select(src => src.Item2); | |
| } | |
| private Dictionary<string, ProjectConfiguration> configurations; | |
| // type_name, path | |
| private List<Tuple<string, string>> source_items; | |
| } | |
| public class VCProjectParser | |
| { | |
| public const string PARSER_NODE_ITEM_GROUP = "ItemGroup"; | |
| public const string PARSER_NODE_PROPERTY_GROUP = "PropertyGroup"; | |
| public const string PARSER_NODE_IMPORT = "Import"; | |
| public const string PARSER_NODE_IMPORT_GROUP = "ImportGroup"; | |
| public const string PARSER_NODE_ITEM_DEFINITION_GROUP = "ItemDefinitionGroup"; | |
| public const string PARSER_NODE_PCH = "PrecompiledHeader"; | |
| public const string PARSER_NODE_EXCLUDE = "ExcludedFromBuild"; | |
| //const string attr_key = "Include", inc_key = "ClInclude", src_key = "ClCompile", rc_key = "ResourceCompile"; | |
| public const string PARSER_SOURCE_ITEM_TYPE_INC = "ClInclude"; | |
| public const string PARSER_SOURCE_ITEM_TYPE_SRC = "ClCompile"; | |
| public const string PARSER_SOURCE_ITEM_TYPE_RES = "ResourceCompile"; | |
| private const string PARSER_SOURCE_ITEM_ATTR = "Include"; | |
| private const string CONFIGURATION_PREFIX = "'$(Configuration)|$(Platform)'=="; | |
| //TODO: rule based parsing (maybe overkill... | |
| private string file; | |
| public VCProjectParser(string file) | |
| { | |
| this.file = file; | |
| } | |
| public ProjectInfo parse() | |
| { | |
| XElement root = XElement.Load(file); | |
| //sax or one pass visitor can gain more performance, but I don't intend to do that, maybe you can do it. | |
| //you may noticed there are so many string literals, if you feel sick, clean it :> | |
| var project_info = (from prop in root.ElementsAnyNS(PARSER_NODE_PROPERTY_GROUP) | |
| where prop.Attribute("Label")?.Value == "Globals" | |
| select new ProjectInfo( | |
| prop.ElementAnyNS("RootNamespace").Value, | |
| prop.ElementAnyNS("ProjectGuid").Value | |
| )).FirstOrDefault(); | |
| //init config | |
| (from prop in root.ElementsAnyNS(PARSER_NODE_ITEM_GROUP) | |
| where prop.Attribute("Label")?.Value == "ProjectConfigurations" | |
| select prop) | |
| .ElementsAnyNS("ProjectConfiguration") | |
| .ForEach(p => | |
| { | |
| //Include -> $(Configuration)|$(Platform) | |
| Console.WriteLine(p); | |
| project_info.add_config(p.Attribute("Include")?.Value, new ProjectConfiguration(p.ElementAnyNS("Configuration")?.Value, p.ElementAnyNS("Platform")?.Value)); | |
| }); | |
| Func<XElement, bool> is_condition = e => | |
| { | |
| return e.Attribute("Condition")?.Value.StartsWith(CONFIGURATION_PREFIX) ?? false; | |
| }; | |
| Func<XElement, string> parse_condition = e => | |
| { | |
| return e.Attribute("Condition")?.Value?.Replace(CONFIGURATION_PREFIX, "")?.Replace("'", ""); ; | |
| }; | |
| //Visual studio PropertySheets importing is not supported currently... | |
| //use node visitor to reduce further pass.... | |
| Action<string, XElement> property_visitor = (cfg, e) => | |
| { | |
| e.Elements().ForEach(prop => | |
| { | |
| project_info.get_config(cfg)?.set_property(prop.Name.LocalName, prop.Value); | |
| }); | |
| }; | |
| Action<string, XElement> build_option_visitor = (cfg, e) => | |
| { | |
| //ClCompile, Link, ResourceCompile, res | |
| //Build events are ignored | |
| e.MultiNodeDispatch(new List<Tuple<string, Action<XElement>>> { | |
| Tuple.Create<string, Action<XElement>>("ClCompile", elem => { | |
| elem.Elements().ForEach(prop => { | |
| project_info.get_config(cfg)?.add_compile_option(prop.Name.LocalName, prop.Value); | |
| }); | |
| }), | |
| Tuple.Create<string, Action<XElement>>("Link", elem => { | |
| elem.Elements().ForEach(prop => { | |
| project_info.get_config(cfg)?.add_link_option(prop.Name.LocalName, prop.Value); | |
| }); | |
| }), | |
| Tuple.Create<string, Action<XElement>>("Lib", elem => { | |
| elem.Elements().ForEach(prop => { | |
| project_info.get_config(cfg)?.add_lib_option(prop.Name.LocalName, prop.Value); | |
| }); | |
| }), | |
| Tuple.Create<string, Action<XElement>>("ResourceCompile", elem => { | |
| elem.Elements().ForEach(prop => { | |
| project_info.get_config(cfg)?.add_res_option(prop.Name.LocalName, prop.Value); | |
| }); | |
| }), | |
| Tuple.Create<string, Action<XElement>>("Midl", elem => { | |
| project_info.get_config(cfg)?.set_property("Midl", elem.Value); | |
| }) | |
| }); | |
| }; | |
| Action<XElement> source_items_visitor = (e) => | |
| { | |
| // configurations[cfg] = e.ElementsAnyNS("ClInclude")?.Select(item => { return item.Attribute("Include")?.Value; }) | |
| //excludes and precompiled headers | |
| Action<string, XElement> process_special_source = (key, itm) => | |
| { | |
| var path = itm.Attribute(PARSER_SOURCE_ITEM_ATTR)?.Value; | |
| itm.SingleNodeDispatch(PARSER_NODE_EXCLUDE, elem => | |
| { | |
| var config_name = parse_condition(elem); | |
| if (elem.Value == "true") | |
| { | |
| project_info.get_config(config_name)?.add_exclude(key, path); | |
| } | |
| }); | |
| itm.MultiNodeDispatch(new List<Tuple<string, Action<XElement>>> { | |
| Tuple.Create<string, Action<XElement>>(PARSER_NODE_EXCLUDE, (_itm) => { | |
| var config_name = parse_condition(_itm); | |
| if (_itm.Value == "true") | |
| { | |
| project_info.get_config(config_name)?.add_exclude(key, path); | |
| } | |
| }), | |
| Tuple.Create<string, Action<XElement>>(PARSER_NODE_PCH, (_itm) => { | |
| var config_name = parse_condition(_itm); | |
| if (_itm.Value == "Create") | |
| { | |
| project_info.get_config(config_name)?.set_property(PARSER_NODE_PCH, path); | |
| } | |
| }) | |
| }); | |
| }; | |
| Action<string, XElement> process_source = (key, itm) => | |
| { | |
| project_info.add_source(key, itm.Attribute(PARSER_SOURCE_ITEM_ATTR)?.Value); | |
| }; | |
| e.MultiNodeDispatch(new List<Tuple<string, Action<XElement>>> | |
| { | |
| Tuple.Create<string, Action<XElement>>(PARSER_SOURCE_ITEM_TYPE_INC, (itm) => { | |
| process_source(PARSER_SOURCE_ITEM_TYPE_INC, itm); | |
| process_special_source(PARSER_SOURCE_ITEM_TYPE_INC, itm); | |
| }), | |
| Tuple.Create<string, Action<XElement>>(PARSER_SOURCE_ITEM_TYPE_SRC, (itm) => { | |
| process_source(PARSER_SOURCE_ITEM_TYPE_SRC, itm); | |
| process_special_source(PARSER_SOURCE_ITEM_TYPE_SRC, itm); | |
| }), | |
| Tuple.Create<string, Action<XElement>>(PARSER_SOURCE_ITEM_TYPE_RES, (itm) => { | |
| process_source(PARSER_SOURCE_ITEM_TYPE_RES, itm); | |
| process_special_source(PARSER_SOURCE_ITEM_TYPE_RES, itm); | |
| }) | |
| }); | |
| //project_info.add_sources(inc_key, e.ElementsAnyNS(inc_key)?.Select(item => { return item.Attribute(attr_key)?.Value; })); | |
| //project_info.add_sources(src_key, e.ElementsAnyNS(src_key)?.Select(item => { return item.Attribute(attr_key)?.Value; })); | |
| //project_info.add_sources(rc_key, e.ElementsAnyNS(rc_key)?.Select(item => { return item.Attribute(attr_key)?.Value; })); | |
| }; | |
| root.MultiNodeDispatch(new List<Tuple<string, Action<XElement>>> | |
| { | |
| Tuple.Create<string, Action<XElement>>(PARSER_NODE_PROPERTY_GROUP, (itm) => { | |
| if(is_condition(itm)) | |
| { | |
| property_visitor(parse_condition(itm), itm); | |
| } | |
| }), | |
| Tuple.Create<string, Action<XElement>>(PARSER_NODE_ITEM_GROUP, (itm) => { | |
| source_items_visitor(itm); | |
| }), | |
| Tuple.Create<string, Action<XElement>>(PARSER_NODE_ITEM_DEFINITION_GROUP, (itm) => { | |
| if(is_condition(itm)) | |
| { | |
| build_option_visitor(parse_condition(itm), itm); | |
| } | |
| }), | |
| }); | |
| return project_info; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment