Skip to content

Instantly share code, notes, and snippets.

@ifree
Last active June 3, 2023 12:49
Show Gist options
  • Select an option

  • Save ifree/16ea5d9f984843f8774deea76a85b007 to your computer and use it in GitHub Desktop.

Select an option

Save ifree/16ea5d9f984843f8774deea76a85b007 to your computer and use it in GitHub Desktop.
When I realized there is a thing called msbuild, I orz...
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;
}
}
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