using System; using System.Collections; using System.IO; using Premake.Tests.Framework; namespace Premake.Tests.Vs2005 { public class Vs2005Parser : Parser { private int _numAspNet = 0; #region Parser Methods public override string TargetName { get { return "vs2005"; } } #endregion #region Solution Parsing public override void Parse(Project project, string filename) { /* File header */ Begin(filename + ".sln"); Match("Microsoft Visual Studio Solution File, Format Version 9.00"); Match("# Visual Studio 2005"); /* Package entries - VS "projects" */ string[] matches; Hashtable packageDependencies = new Hashtable(); while (!Match("Global", true)) { matches = Regex("Project\\(\"{([0-9A-F-]+)}\"\\) = \"(.+)\", \"(.+)\", \"{([0-9A-F-]+)}\""); Package package = new Package(); project.Package.Add(package); package.Name = matches[1]; package.ID = matches[3]; package.Path = Path.GetDirectoryName(matches[2]); package.ScriptName = Path.GetFileName(matches[2]); switch (matches[0]) { case "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942": package.Language = "c++"; break; case "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC": package.Language = "c#"; break; case "E24C65DC-7377-472B-9ABA-BC803B73C61A": package.Kind = "aspnet"; break; } if (package.Kind == "aspnet") { Match("\tProjectSection(WebsiteProperties) = preProject"); ArrayList deps = new ArrayList(); matches = Regex("\t\tProjectReferences = \"{([0-9A-F-]+)}|(.+).dll;\"", true); while (matches != null) { deps.Add(matches[0]); matches = Regex("\t\tProjectReferences = \"{([0-9A-F-]+)}|(.+).dll;\"", true); } packageDependencies[package.ID] = deps; matches = Regex("\t\t(.+).AspNetCompiler.VirtualPath = \"/" + package.Name + "\"", true); while (matches != null) { string cfg = matches[0]; Match("\t\t" + cfg + ".AspNetCompiler.PhysicalPath = \"" + package.Path + "\\\""); Match("\t\t" + cfg + ".AspNetCompiler.TargetPath = \"PrecompiledWeb\\" + package.Name + "\\\""); Match("\t\t" + cfg + ".AspNetCompiler.Updateable = \"true\""); Match("\t\t" + cfg + ".AspNetCompiler.ForceOverwrite = \"true\""); Match("\t\t" + cfg + ".AspNetCompiler.FixedNames = \"false\""); matches = Regex("\t\t" + cfg + ".AspNetCompiler.Debug = \"(True|False)\""); matches = Regex("\t\t(.+).AspNetCompiler.VirtualPath = \"/" + package.Name + "\"", true); } int port; if (_numAspNet == 0) port = 1106; else if (_numAspNet == 1) port = 1231; else port = 1251 + 2 * (_numAspNet - 2); Match("\t\tVWDPort = \"" + port + "\""); _numAspNet++; matches = Regex("\t\tDefaultWebSiteLanguage = \"(.+)\""); if (matches == null) package.Language = "vbnet"; else if (matches[0] == "Visual C#") package.Language = "c#"; Match("\tEndProjectSection"); } else { /* Collect package dependencies, I'll sort them out after * I finish parsing the scripts below */ Match("\tProjectSection(ProjectDependencies) = postProject"); ArrayList deps = new ArrayList(); while (!Match("\tEndProjectSection", true)) { matches = Regex("\t\t{([0-9A-F-]+)} = {([0-9A-F-]+)}"); if (matches[0] != matches[1]) throw new FormatException("GUID mismatch in ProjectDependencies block, should be the same"); deps.Add(matches[0]); } packageDependencies[package.ID] = deps; } Match("EndProject"); } /* Read the list of configurations...just cache for now, test later */ ArrayList possibleConfigs = new ArrayList(); Match("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution"); while (!Match("\tEndGlobalSection", true)) { matches = Regex("\t\t(.+)[ ]=[ ](.+)", true); string cfg = matches[0]; if (possibleConfigs.Contains(cfg)) throw new FormatException("Duplicate configuration '" + cfg + "'"); possibleConfigs.Add(cfg); /* Build up my list of scripted package configurations */ matches = cfg.Split('|'); if (!project.Configuration.Contains(matches[0])) project.Configuration.Add(matches[0]); } foreach (Package package in project.Package) package.Config.Add(project); /* Read the list of package configurations...cache and test later */ ArrayList packageConfigs = new ArrayList(); Match("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution"); while (!Match("\tEndGlobalSection", true)) { string cfg = GetLine(); if (packageConfigs.Contains(cfg)) throw new FormatException("Duplicate configuration '" + cfg + "'"); packageConfigs.Add(cfg); } Match("\tGlobalSection(SolutionProperties) = preSolution"); Match("\t\tHideSolutionNode = FALSE"); Match("\tEndGlobalSection"); Match("EndGlobal"); foreach (Package package in project.Package) { if (package.Kind != "aspnet") { filename = Path.Combine(Path.Combine(project.Path, package.Path), package.ScriptName); switch (package.Language) { case "c++": ParseCpp(project, package, filename); break; case "c#": ParseCs(project, package, filename); break; default: throw new NotImplementedException("Loading of " + package.Language + " packages not implemented"); } } else { foreach (Configuration config in package.Config) config.Kind = "aspnet"; } } /* Now sort out the inter-package dependencies */ foreach (Package package in project.Package) { ArrayList deps = (ArrayList)packageDependencies[package.ID]; string[] deplist = new string[deps.Count]; for (int i = 0; i < deps.Count; ++i) { foreach (Package p2 in project.Package) { if (p2.ID == (string)deps[i]) deplist[i] = p2.Name; } } foreach (Configuration config in package.Config) config.Dependencies = deplist; } /* Now test configurations */ bool hasDotNet = false; bool hasCpp = false; foreach (Package package in project.Package) { if (package.Language == "c" || package.Language == "c++") hasCpp = true; else hasDotNet = true; } ArrayList platforms = new ArrayList(); if (hasDotNet) platforms.Add("Any CPU"); if (hasDotNet && hasCpp) platforms.Add("Mixed Platforms"); if (hasCpp) platforms.Add("Win32"); int c = 0; foreach (Package package in project.Package) { string arch; if (package.Language == "c" || package.Language == "c++") arch = "Win32"; else arch = "Any CPU"; foreach (Configuration config in package.Config) { foreach (string platform in platforms) { string configPlatform = config.Name + "|" + platform; if (!possibleConfigs.Contains(configPlatform)) throw new FormatException(configPlatform + " is not listed in possible configuration"); string root = "\t\t{" + package.ID + "}." + configPlatform + "."; string match = root + "ActiveCfg = " + config.Name + "|" + arch; if (((string)packageConfigs[c]) != match) throw new FormatException("Expected:\n " + match + "\nBut I got:\n " + packageConfigs[c]); c++; if (platform == "MixedPlatforms" || platform == arch) { match = root + "Build.0 = " + config.Name + "|" + arch; if (((string)packageConfigs[c]) != match) throw new FormatException("Expected:\n " + match + "\nBut I got:\n " + packageConfigs[c]); c++; } } } } } #endregion #region C++ Parsing private void ParseCpp(Project project, Package package, string filename) { Begin(filename); Match(""); Match(""); Match("\t"); Match("\t\t"); Match("\t"); Match("\t"); Match("\t"); Match("\t"); foreach (Configuration config in package.Config) { ArrayList buildFlags = new ArrayList(); Match("\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t 0) Match("\t\t\t\tStringPooling=\"true\"", true); if (optimization == 0) Match("\t\t\t\tBasicRuntimeChecks=\"3\"", true); if (optimization == 0) matches = Regex("\t\t\t\tRuntimeLibrary=\"(1|3)\""); else matches = Regex("\t\t\t\tRuntimeLibrary=\"(0|2)\""); if (matches[0] == "0" || matches[0] == "1") config.LinkFlags = new string[] { "static-runtime" }; Match("\t\t\t\tEnableFunctionLevelLinking=\"true\""); if (Match("\t\t\t\tRuntimeTypeInfo=\"false\"", true)) buildFlags.Add("no-rtti"); matches = Regex("\t\t\t\tTreatWChar_tAsBuiltInType=\"(true|false)\"", true); if (matches != null) buildFlags.Add(matches[0] == "true" ? "native-wchar" : "no-native-wchar"); config.Pch = "off"; matches = Regex("\t\t\t\tUsePrecompiledHeader=\"([0-9])\""); if (matches[0] == "2") { config.Pch = "on"; matches = Regex("\t\t\t\tPrecompiledHeaderThrough=\"(.+?)\""); config.PchHeader = matches[0]; } else if (matches[0] != "0") { throw new FormatException("Expected UsePrecompiledHeader to be 2, got " + matches[0]); } matches = Regex("\t\t\t\tWarningLevel=\"([3-4])\""); if (matches[0] == "4") buildFlags.Add("extra-warnings"); if (Match("\t\t\t\tWarnAsError=\"true\"", true)) buildFlags.Add("fatal-warnings"); matches = Regex("\t\t\t\tDetect64BitPortabilityProblems=\"(true|false)\""); if (matches[0] == "false") buildFlags.Add("no-64bit-checks"); matches = Regex("\t\t\t\tDebugInformationFormat=\"([0-9])\""); if (matches[0] == "0") buildFlags.Add("no-symbols"); else if (matches[0] == "3") buildFlags.Add("no-edit-and-continue"); Match("\t\t\t/>"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t 0) { Match("\t\t\t\tOptimizeReferences=\"2\""); Match("\t\t\t\tEnableCOMDATFolding=\"2\""); } if (config.Kind == "exe" || config.Kind == "winexe") { if (!Match("\t\t\t\tEntryPointSymbol=\"mainCRTStartup\"", true)) buildFlags.Add("no-main"); } else { matches = Regex("\t\t\t\tImportLibrary=\"(.+)\""); config.ImportLib = matches[0]; } Match("\t\t\t\tTargetMachine=\"1\""); } Match("\t\t\t/>"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t\t"); Match("\t\t"); config.BuildFlags = (string[])buildFlags.ToArray(typeof(string)); } Match("\t"); Match("\t"); Match("\t"); Match("\t"); string indent = "\t"; string folder = ""; while (!Match("\t", true)) { if (Match(indent + "\t"); } else if (Match(indent + "", true)) { indent = indent.Substring(0, indent.Length - 1); folder = Path.GetDirectoryName(folder); } else { Match(indent + "\t]?)"); package.File.Add(matches[0]); /* Make sure file appears in the correct folder */ filename = matches[0]; if (filename.StartsWith(".\\")) filename = filename.Substring(2); while (filename.StartsWith("..\\")) filename = filename.Substring(3); if (Path.GetDirectoryName(filename) != folder) throw new FormatException("File '" + matches[0] + "' is in folder '" + folder + "'"); /* Check for file configuration section */ if (matches[1] == "") { Match(indent + "\t\t>"); foreach (Configuration config in package.Config) { config.PchSource = filename; Match(indent + "\t\t"); Match(indent + "\t\t\t"); Match(indent + "\t\t"); } } Match(indent + "\t"); } } if (indent != "\t") throw new FormatException("Unclosed entity in block"); Match("\t"); Match("\t"); Match(""); } #endregion #region C# Parsing private void ParseCs(Project project, Package package, string filename) { string kind = package.Kind; Begin(filename); Match(""); Match(" "); string[] matches; matches = Regex(" (.+)"); string defaultConfigName = matches[0]; Match(" AnyCPU"); Match(" 8.0.50727"); Match(" 2.0"); Match(" {" + package.ID + "}"); matches = Regex(" (.+)"); if (kind == null) { switch (matches[0]) { case "Exe": kind = "exe"; break; case "WinExe": kind = "winexe"; break; case "Library": kind = "dll"; break; default: throw new FormatException("Unexpected value: OutputType = \"" + matches[0] + "\""); } } else if (matches[0] != "Library") { throw new FormatException("Output type must be 'library' for ASP.NET applications"); } Match(" Properties"); matches = Regex("(.+)"); string target = matches[0]; Match(" " + target + ""); Match(" "); foreach (Configuration config in package.Config) { ArrayList buildFlags = new ArrayList(); config.Kind = kind; config.Target = target; Match(" "); if (!Match(" true", true)) { buildFlags.Add("no-symbols"); Match(" pdbonly"); } else { Match(" full"); } matches = Regex(" (true|false)"); if (matches[0] == "true") buildFlags.Add("optimize"); matches = Regex(" (.*)\\\\"); config.BinDir = matches[0]; config.OutDir = matches[0]; matches = Regex(" (.*)"); config.Defines = matches[0].Split(';'); if (config.Defines[0] == String.Empty) config.Defines = new string[] { }; Match(" prompt"); Match(" 4"); if (Match(" true", true)) buildFlags.Add("unsafe"); if (Match(" true", true)) buildFlags.Add("fatal-warnings"); Match(" "); config.BuildFlags = (string[])buildFlags.ToArray(typeof(string)); } ArrayList links = new ArrayList(); Match(" "); while (!Match(" ", true)) { matches = Regex(" ", true); if (matches != null) { string name = matches[0]; links.Add(name); } matches = Regex(" ", true); if (matches != null) { matches = Regex(" {([0-9A-F-]+)}"); matches = Regex(" (.+)"); Match(" "); string name = matches[0]; links.Add(name); } } foreach (Configuration config in package.Config) { config.Links = (string[])links.ToArray(typeof(string)); } Match(" "); while (!Match(" ", true)) { matches = Regex(" <(.+) Include=\"(.+)\"(>| />)"); string action = matches[0]; string name = matches[1]; string endtag = matches[2]; string subtype = ""; string depends = ""; if (action == "Compile" && endtag == ">") { subtype = "code"; while (!Match(" ", true)) { matches = Regex(" (.+)", true); if (matches != null) subtype = matches[0]; if (Match(" True", true)) subtype = "autogen"; matches = Regex(" (.+)", true); if (matches != null) depends = matches[0]; } } else { subtype = "code"; } if (action == "EmbeddedResource" && endtag == ">") { while (!Match(" ", true)) { matches = Regex(" (.+)", true); if (matches != null) subtype = matches[0]; matches = Regex(" (.+)", true); if (matches != null) depends = matches[0]; if (Match(" ResXFileCodeGenerator", true)) { subtype = "autogen"; Match(" " + Path.GetFileNameWithoutExtension(name) + ".Designer.cs"); } } } if (action == "Content") { Match(" PreserveNewest"); Match(" "); } package.File.Add(name, subtype, action, depends); } Match(" "); Match(" "); Match(""); if (kind != "aspnet") ParseUserFile(project, package, filename); } private void ParseUserFile(Project project, Package package, string filename) { Begin(filename + ".user"); Match(""); Match(" "); string[] matches = Regex(" (.+)"); matches = matches[0].Split(';'); string[] libpaths = new string[matches.Length - 1]; /* VS.NET stores reference directories as absolute paths, so I need * to do some ugly trickery here */ string[] bindir = matches[matches.Length - 1].Split('\\'); for (int i = 0; i < matches.Length - 1; ++i) { string[] thisdir = matches[i].Split('\\'); int j = 0; while (j < bindir.Length && j < thisdir.Length && bindir[j] == thisdir[j]) ++j; string path = ""; for (int k = j + 1; k < bindir.Length; ++k) path += "..\\"; for (int k = j; k < thisdir.Length; ++k) path += thisdir[k] + '\\'; libpaths[i] = path.Substring(0, path.Length - 1); libpaths[i] = libpaths[i].Replace("/", "\\"); } foreach (Configuration config in package.Config) { config.LibPaths = libpaths; } Match(" "); Match(""); } #endregion } }