1 using System;
2 using System.Collections;
3 using System.IO;
4 using Premake.Tests.Framework;
5 
6 namespace Premake.Tests.Vs6
7 {
8 	public class Vs6Parser : Parser
9 	{
10 		#region Parser Methods
11 		public override string TargetName
12 		{
13 			get { return "vs6"; }
14 		}
15 		#endregion
16 
17 		#region Workspace Parsing
18 
Parse(Project project, string filename)19 		public override void Parse(Project project, string filename)
20 		{
21 			/* File header */
22 			Begin(filename + ".dsw");
23 
24 			Match("Microsoft Developer Studio Workspace File, Format Version 6.00");
25 			Match("# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!");
26 			Match("");
27 			Match("###############################################################################");
28 			Match("");
29 
30 			Hashtable dependencies = new Hashtable();
31 			string[] matches;
32 			while (!Match("Global:", true))
33 			{
34 				Package package = new Package();
35 				project.Package.Add(package);
36 
37 				matches = Regex("Project: \"(.+)\"=(.+) - Package Owner=<4>");
38 				package.Name       = matches[0];
39 				package.Path       = Path.GetDirectoryName(matches[1]);
40 				package.ScriptName = Path.GetFileName(matches[1]);
41 				package.Language   = "c++";
42 				Match("");
43 
44 				Match("Package=<5>");
45 				Match("{{{");
46 				Match("}}}");
47 				Match("");
48 				Match("Package=<4>");
49 				Match("{{{");
50 
51 				/* Get the package dependencies, cache until I have configs */
52 				ArrayList deps = new ArrayList();
53 				while (Match("    Begin Project Dependency", true))
54 				{
55 					matches = Regex("    Project_Dep_Name (.+)");
56 					deps.Add(matches[0]);
57 					Match("    End Project Dependency");
58 				}
59 				dependencies[package] = (string[])deps.ToArray(typeof(string));
60 
61 				Match("}}}");
62 				Match("");
63 				Match("###############################################################################");
64 				Match("");
65 			}
66 
67 			Match("");
68 			Match("Package=<5>");
69 			Match("{{{");
70 			Match("}}}");
71 			Match("");
72 			Match("Package=<3>");
73 			Match("{{{");
74 			Match("}}}");
75 			Match("");
76 			Match("###############################################################################");
77 			Match("");
78 
79 			foreach (Package package in project.Package)
80 			{
81 				filename = Path.Combine(Path.Combine(project.Path, package.Path), package.ScriptName);
82 				switch (package.Language)
83 				{
84 				case "c++":
85 					ParseCpp(project, package, filename);
86 					break;
87 				default:
88 					throw new NotImplementedException("Loading of " + package.Language + " packages not implemented");
89 				}
90 
91 				ArrayList temp = new ArrayList();
92 				foreach (Configuration config in package.Config)
93 				{
94 					config.Dependencies = (string[])dependencies[package];
95 					temp.Add(config);
96 				}
97 
98 				/* VS6 stores configs in reverse order */
99 				temp.Reverse();
100 				for (int i = 0; i < package.Config.Count; ++i)
101 					package.Config[i] = (Configuration)temp[i];
102 			}
103 		}
104 
105 		#endregion
106 
107 		#region C++ Parsing
108 
ParseCpp(Project project, Package package, string filename)109 		private void ParseCpp(Project project, Package package, string filename)
110 		{
111 			Begin(filename);
112 			Match("# Microsoft Developer Studio Project File - Name=\"" + package.Name + "\" - Package Owner=<4>");
113 			Match("# Microsoft Developer Studio Generated Build File, Format Version 6.00");
114 			Match("# ** DO NOT EDIT **");
115 			Match("");
116 
117 			string[] matches = Regex("# TARGTYPE (.+)");
118 			switch (matches[0])
119 			{
120 			case "\"Win32 (x86) Application\" 0x0101":
121 				package.Kind = "winexe";
122 				break;
123 			case "\"Win32 (x86) Console Application\" 0x0103":
124 				package.Kind = "exe";
125 				break;
126 			case "\"Win32 (x86) Dynamic-Link Library\" 0x0102":
127 				package.Kind = "dll";
128 				break;
129 			case "\"Win32 (x86) Static Library\" 0x0104":
130 				package.Kind = "lib";
131 				break;
132 			default:
133 				throw new FormatException("Unrecognized target type: '" + matches[0] + "'");
134 			}
135 			string baseKind = matches[0].Substring(0, matches[0].Length - 7);
136 			Match("");
137 
138 			matches = Regex("CFG=" + package.Name + " - (.+)");
139 			string defaultConfig = matches[0];
140 
141 			Match("!MESSAGE This is not a valid makefile. To build this project using NMAKE,");
142 			Match("!MESSAGE use the Export Makefile command and run");
143 			Match("!MESSAGE ");
144 			Match("!MESSAGE NMAKE /f \"" + package.Name + ".mak\".");
145 			Match("!MESSAGE ");
146 			Match("!MESSAGE You can specify a configuration when running NMAKE");
147 			Match("!MESSAGE by defining the macro CFG on the command line. For example:");
148 			Match("!MESSAGE ");
149 			Match("!MESSAGE NMAKE /f \"" + package.Name + ".mak\" CFG=\"" + package.Name + " - " + defaultConfig + "\"");
150 			Match("!MESSAGE ");
151 			Match("!MESSAGE Possible choices for configuration are:");
152 			Match("!MESSAGE ");
153 
154 			baseKind = baseKind.Replace("(", "\\(");
155 			baseKind = baseKind.Replace(")", "\\)");
156 			while (!Match("!MESSAGE ", true))
157 			{
158 				matches = Regex("!MESSAGE \"" + package.Name + " - ([^\"]+)\" \\(based on " + baseKind + "\\)");
159 
160 				Configuration config = new Configuration();
161 				config.Name = matches[0];
162 				config.Dependencies = new string[0];
163 				package.Config.Add(config);
164 			}
165 			Match("");
166 
167 			/* Verify configurations */
168 			if (project.Configuration.Count == 0)
169 			{
170 				foreach (Configuration config in package.Config)
171 					project.Configuration.Add(config.Name);
172 			}
173 			else
174 			{
175 				foreach (Configuration config in package.Config)
176 					if (!project.Configuration.Contains(config.Name))
177 						throw new FormatException("Config '" + config.Name + "' not found in project");
178 			}
179 
180 			Match("# Begin Project");
181 			Match("# PROP AllowPerConfigDependencies 0");
182 			Match("# PROP Scc_ProjName \"\"");
183 			Match("# PROP Scc_LocalPath \"\"");
184 			Match("CPP=cl.exe");
185 			if (package.Kind != "lib")
186 				Match("MTL=midl.exe");
187 			Match("RSC=rc.exe");
188 			Match("");
189 
190 			bool first = true;
191 			foreach (Configuration config in package.Config)
192 			{
193 				ArrayList bldflags = new ArrayList();
194 				ArrayList lnkflags = new ArrayList();
195 				ArrayList defines  = new ArrayList();
196 				ArrayList incpaths = new ArrayList();
197 				ArrayList links    = new ArrayList();
198 				ArrayList libpaths = new ArrayList();
199 
200 				string cond = (first) ? "!IF" : "!ELSEIF";
201 				first = false;
202 
203 				Match(cond + "  \"$(CFG)\" == \"" + package.Name + " - " + config.Name + "\"");
204 				Match("");
205 
206 				Match("# PROP BASE Use_MFC 0");
207 
208 				matches = Regex("# PROP BASE Use_Debug_Libraries ([0-1])");
209 				string useDebugLib = matches[0];
210 
211 				matches = Regex("# PROP BASE Output_Dir \"(.+)\"");
212 				config.OutDir = matches[0];
213 				config.BinDir = matches[0];
214 				config.LibDir = matches[0];
215 
216 				matches = Regex("# PROP BASE Intermediate_Dir \"(.+)\"");
217 				config.ObjDir = matches[0];
218 
219 				Match("# PROP BASE Target_Dir \"\"");
220 				Match("# PROP Use_MFC 0");
221 				Match("# PROP Use_Debug_Libraries " + useDebugLib);
222 				Match("# PROP Output_Dir \"" + config.OutDir + "\"");
223 				Match("# PROP Intermediate_Dir \"" + config.ObjDir + "\"");
224 
225 				if (Match("# PROP Ignore_Export_Lib 1", true))
226 					bldflags.Add("no-import-lib");
227 
228 				Match("# PROP Target_Dir \"\"");
229 
230 				matches = Regex("# ADD BASE CPP /nologo (.+)");
231 				string flags = matches[0];
232 				matches = flags.Split(' ');
233 
234 				int i = 0;
235 				if (matches[i] == "/MTd" || matches[i] == "/MT")
236 				{
237 					lnkflags.Add("static-runtime");
238 				}
239 				else
240 				{
241 					Expect(matches[i], "/MDd", "/MD");
242 				}
243 				++i;
244 
245 				if (matches[i] == "/W4")
246 				{
247 					bldflags.Add("extra-warnings");
248 				}
249 				else
250 				{
251 					Expect(matches[i], "/W3");
252 				}
253 				++i;
254 
255 				if (matches[i] == "/WX")
256 				{
257 					bldflags.Add("fatal-warnings");
258 					++i;
259 				}
260 
261 				if (matches[i] == "/Gm")
262 				{
263 					++i;
264 				}
265 
266 				if (matches[i] == "/GR")
267 				{
268 					++i;
269 				}
270 				else
271 				{
272 					bldflags.Add("no-rtti");
273 				}
274 
275 				if (matches[i] == "/GX")
276 				{
277 					++i;
278 				}
279 				else
280 				{
281 					bldflags.Add("no-exceptions");
282 				}
283 
284 				if (matches[i] == "/ZI")
285 				{
286 					++i;
287 				}
288 				else
289 				{
290 					bldflags.Add("no-symbols");
291 				}
292 
293 				if (matches[i] == "/O1")
294 				{
295 					bldflags.Add("optimize-size");
296 				}
297 				else if (matches[i] == "/O2")
298 				{
299 					bldflags.Add("optimize");
300 				}
301 				else
302 				{
303 					Expect(matches[i], "/Od");
304 				}
305 				++i;
306 
307 				if (matches[i] == "/Oy")
308 				{
309 					bldflags.Add("no-frame-pointer");
310 					++i;
311 				}
312 
313 				while (matches[i] == "/I")
314 				{
315 					incpaths.Add(matches[++i].Trim('\"'));
316 					++i;
317 				}
318 
319 				while (matches[i] == "/D")
320 				{
321 					defines.Add(matches[++i].Trim('\"'));
322 					++i;
323 				}
324 
325 				Expect(matches[i], "/YX");
326 				Expect(matches[++i], "/FD");
327 				if (matches[++i] == "/GZ")
328 					++i;
329 				Expect(matches[i++], "/c");
330 
331 				config.BuildOptions = String.Empty;
332 				while (i < matches.Length)
333 					config.BuildOptions += " " + matches[i++];
334 				config.BuildOptions = config.BuildOptions.Trim();
335 
336 				Match("# ADD CPP /nologo " + flags);
337 
338 				string debugSymbol = bldflags.Contains("no-symbols") ? "NDEBUG" : "_DEBUG";
339 
340 				bool hasMtl = Match("# ADD BASE MTL /nologo /D \"" + debugSymbol + "\" /mktyplib203 /win32", true);
341 				if (hasMtl)
342 					Match("# ADD MTL /nologo /D \"" + debugSymbol + "\" /mktyplib203 /win32");
343 
344 				ArrayList resDefines = new ArrayList();
345 				ArrayList resPaths = new ArrayList();
346 				matches = Regex("# ADD BASE RSC /l 0x409 (.+)");
347 				while (matches[0].Length > 0)
348 				{
349 					if (matches[0].StartsWith("/d") || matches[0].StartsWith("/i"))
350 					{
351 						int split = matches[0].IndexOf('"', 4);
352 						string value = matches[0].Substring(4, split - 4);
353 						if (matches[0].StartsWith("/d"))
354 							resDefines.Add(value);
355 						else
356 							resPaths.Add(value);
357 						matches[0] = matches[0].Substring(split + 1).TrimStart();
358 					}
359 					else
360 					{
361 						config.ResOptions = matches[0];
362 						matches[0] = "";
363 					}
364 				}
365 
366 				config.ResDefines = (string[])resDefines.ToArray(typeof(string));
367 				config.ResPaths = (string[])resPaths.ToArray(typeof(string));
368 
369 				string rsc = "# ADD RSC /l 0x409";
370 				foreach (string symbol in resDefines)
371 					rsc += " /d \"" + symbol + "\"";
372 				foreach (string symbol in resPaths)
373 					rsc += " /i \"" + symbol + "\"";
374 				if (config.ResOptions != null)
375 					rsc += " " + config.ResOptions;
376 				Match(rsc);
377 
378 				Match("BSC32=bscmake.exe");
379 				Match("# ADD BASE BSC32 /nologo");
380 				Match("# ADD BSC32 /nologo");
381 
382 				if (Match("LINK32=link.exe -lib", true))
383 				{
384 					config.Kind = "lib";
385 					Match("# ADD BASE LIB32 /nologo");
386 					matches = Regex("# ADD LIB32 /nologo /out:\"(.+)\"", true);
387 					if (matches != null)
388 					{
389 						config.Target = matches[0];
390 					}
391 					else
392 					{
393 						Match("# ADD LIB32 /nologo");
394 					}
395 				}
396 				else
397 				{
398 					Match("LINK32=link.exe");
399 					matches = Regex("# ADD BASE LINK32 (.+)");
400 					flags = matches[0];
401 					matches = flags.Split(' ');
402 
403 					i = 0;
404 					while (i < matches.Length && matches[i][0] != '/')
405 						links.Add(matches[i++]);
406 
407 					Expect(matches[i++], "/nologo");
408 
409 					bool noMain = true;
410 					if (matches[i] == "/entry:\"mainCRTStartup\"")
411 					{
412 						noMain = false;
413 						++i;
414 					}
415 
416 					if (matches[i] == "/subsystem:windows")
417 					{
418 						config.Kind = "winexe";
419 					}
420 					else if (matches[i] == "/subsystem:console")
421 					{
422 						config.Kind = "exe";
423 					}
424 					else if (matches[i] == "/dll")
425 					{
426 						config.Kind = "dll";
427 					}
428 					else
429 					{
430 						throw new FormatException("Unexpected subsystem flag: " + matches[i]);
431 					}
432 					++i;
433 
434 					if (hasMtl && config.Kind != "winexe" && config.Kind != "dll")
435 						throw new FormatException("Found unexpected MTL block");
436 
437 					if ((config.Kind == "winexe" || config.Kind == "exe") && noMain)
438 						bldflags.Add("no-main");
439 
440 					if (!bldflags.Contains("no-symbols"))
441 					{
442 						Expect(matches[i++], "/incremental:yes");
443 						Expect(matches[i++], "/debug");
444 					}
445 
446 					Expect(matches[i++], "/machine:I386");
447 
448 					if (config.Kind == "dll")
449 					{
450 						config.ImportLib = matches[i].Substring(9, matches[i].Length - 10);
451 						++i;
452 					}
453 
454 					config.Target = matches[i].Substring(6, matches[i].Length - 7);
455 					if (config.Target.StartsWith(config.OutDir))
456 						config.Target = config.Target.Substring(config.OutDir.Length + 1);
457 					++i;
458 
459 					if (!bldflags.Contains("no-symbols"))
460 						Expect(matches[i++], "/pdbtype:sept");
461 
462 					config.LibDir = matches[i].Substring(10, matches[i].Length - 11);
463 					++i;
464 
465 					while (i < matches.Length && matches[i].StartsWith("/libpath:"))
466 					{
467 						libpaths.Add(matches[i].Substring(10, matches[i].Length - 11));
468 						++i;
469 					}
470 
471 					Match("# ADD LINK32 " + flags);
472 				}
473 
474 				config.BuildFlags = (string[])bldflags.ToArray(typeof(string));
475 				config.LinkFlags  = (string[])lnkflags.ToArray(typeof(string));
476 				config.Defines    = (string[])defines.ToArray(typeof(string));
477 				config.IncludePaths = (string[])incpaths.ToArray(typeof(string));
478 				config.Links      = (string[])links.ToArray(typeof(string));
479 				config.LibPaths   = (string[])libpaths.ToArray(typeof(string));
480 
481 				Match("");
482 			}
483 
484 			Match("!ENDIF");
485 			Match("");
486 			Match("# Begin Target");
487 			Match("");
488 
489 			foreach (Configuration config in package.Config)
490 				Match("# Name \"" + package.Name + " - " + config.Name + "\"");
491 
492 			string folder = "";
493 			while (!Match("# End Target", true))
494 			{
495 				matches = Regex("# Begin Group \"(.+)\"", true);
496 				if (matches != null)
497 				{
498 					folder = Path.Combine(folder, matches[0]);
499 					Match("");
500 					Match("# PROP Default_Filter \"\"");
501 					continue;
502 				}
503 
504 				matches = Regex("# End Group", true);
505 				if (matches != null)
506 				{
507 					folder = Path.GetDirectoryName(folder);
508 					continue;
509 				}
510 
511 				Match("# Begin Source File");
512 				Match("");
513 				matches = Regex("SOURCE=(.+)");
514 
515 				filename = matches[0];
516 				package.File.Add(filename);
517 
518 				if (filename.StartsWith("./"))
519 					filename = filename.Substring(2);
520 				while (filename.StartsWith("../"))
521 					filename = filename.Substring(3);
522 				if (Path.GetDirectoryName(filename) != folder)
523 					throw new FormatException("File '" + matches[0] + "' is in folder '" + folder + "'");
524 
525 				Match("# End Source File");
526 			}
527 		}
528 
529 		#endregion
530 	}
531 }
532