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
}
}