corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 #region BSD License
2 /*
3 Copyright (c) 2004-2008
4 Matthew Holmes (matthew@wildfiregames.com),
5 Dan Moorehead (dan05a@gmail.com),
6 Rob Loach (http://www.robloach.net),
7 C.J. Adams-Collier (cjac@colliertech.org)
8  
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are
11 met:
12  
13 * Redistributions of source code must retain the above copyright
14 notice, this list of conditions and the following disclaimer.
15  
16 * Redistributions in binary form must reproduce the above copyright
17 notice, this list of conditions and the following disclaimer in the
18 documentation and/or other materials provided with the distribution.
19  
20 * The name of the author may not be used to endorse or promote
21 products derived from this software without specific prior written
22 permission.
23  
24 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
25 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
28 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
33 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 POSSIBILITY OF SUCH DAMAGE.
35  
36 */
37 #endregion
38  
39 #region CVS Information
40 /*
41 * $Source$
42 * $Author: dmoonfire $
43 * $Date: 2008-12-09 18:04:22 -0800 (Tue, 09 Dec 2008) $
44 * $Revision: 279 $
45 */
46 #endregion
47  
48 using System;
49 using System.Diagnostics;
50 using System.Collections;
51 using System.Collections.Specialized;
52 using System.IO;
53 using System.Reflection;
54 using System.Xml;
55 using System.Xml.Schema;
56 using System.Text;
57  
58 using Prebuild.Core.Attributes;
59 using Prebuild.Core.Interfaces;
60 using Prebuild.Core.Nodes;
61 using Prebuild.Core.Parse;
62 using Prebuild.Core.Utilities;
63  
64 namespace Prebuild.Core
65 {
66 /// <summary>
67 ///
68 /// </summary>
69 public class Kernel : IDisposable
70 {
71 #region Inner Classes
72  
73 private struct NodeEntry
74 {
75 public Type Type;
76 public DataNodeAttribute Attribute;
77 }
78  
79 #endregion
80  
81 #region Fields
82  
83 private static Kernel m_Instance = new Kernel();
84  
85 /// <summary>
86 /// This must match the version of the schema that is embeeded
87 /// </summary>
88 private static string m_SchemaVersion = "1.7";
89 private static string m_Schema = "prebuild-" + m_SchemaVersion + ".xsd";
90 private static string m_SchemaURI = "http://dnpb.sourceforge.net/schemas/" + m_Schema;
91 bool disposed;
92 private Version m_Version;
93 private string m_Revision = "";
94 private CommandLineCollection m_CommandLine;
95 private Log m_Log;
96 private CurrentDirectory m_CurrentWorkingDirectory;
97 private XmlSchemaCollection m_Schemas;
98  
99 private Hashtable m_Targets;
100 private Hashtable m_Nodes;
101  
102 ArrayList m_Solutions;
103 string m_Target;
104 string m_Clean;
105 string[] m_RemoveDirectories;
106 string m_CurrentFile;
107 XmlDocument m_CurrentDoc;
108 bool m_PauseAfterFinish;
109 string[] m_ProjectGroups;
110 StringCollection m_Refs;
111  
112  
113 #endregion
114  
115 #region Constructors
116  
117 private Kernel()
118 {
119 }
120  
121 #endregion
122  
123 #region Properties
124  
125 /// <summary>
126 /// Gets a value indicating whether [pause after finish].
127 /// </summary>
128 /// <value><c>true</c> if [pause after finish]; otherwise, <c>false</c>.</value>
129 public bool PauseAfterFinish
130 {
131 get
132 {
133 return m_PauseAfterFinish;
134 }
135 }
136  
137 /// <summary>
138 /// Gets the instance.
139 /// </summary>
140 /// <value>The instance.</value>
141 public static Kernel Instance
142 {
143 get
144 {
145 return m_Instance;
146 }
147 }
148  
149 /// <summary>
150 /// Gets the version.
151 /// </summary>
152 /// <value>The version.</value>
153 public string Version
154 {
155 get
156 {
157 return String.Format("{0}.{1}.{2}{3}", m_Version.Major, m_Version.Minor, m_Version.Build, m_Revision);
158 }
159 }
160  
161 /// <summary>
162 /// Gets the command line.
163 /// </summary>
164 /// <value>The command line.</value>
165 public CommandLineCollection CommandLine
166 {
167 get
168 {
169 return m_CommandLine;
170 }
171 }
172  
173 /// <summary>
174 /// Gets the targets.
175 /// </summary>
176 /// <value>The targets.</value>
177 public Hashtable Targets
178 {
179 get
180 {
181 return m_Targets;
182 }
183 }
184  
185 /// <summary>
186 /// Gets the log.
187 /// </summary>
188 /// <value>The log.</value>
189 public Log Log
190 {
191 get
192 {
193 return m_Log;
194 }
195 }
196  
197 /// <summary>
198 /// Gets the current working directory.
199 /// </summary>
200 /// <value>The current working directory.</value>
201 public CurrentDirectory CurrentWorkingDirectory
202 {
203 get
204 {
205 return m_CurrentWorkingDirectory;
206 }
207 }
208  
209 /// <summary>
210 /// Gets the solutions.
211 /// </summary>
212 /// <value>The solutions.</value>
213 public ArrayList Solutions
214 {
215 get
216 {
217 return m_Solutions;
218 }
219 }
220  
221 /// <summary>
222 /// Gets the XmlDocument object representing the prebuild.xml
223 /// being processed
224 /// </summary>
225 /// <value>The XmlDocument object</value>
226 public XmlDocument CurrentDoc
227 {
228 get
229 {
230 return m_CurrentDoc;
231 }
232 }
233  
234 #endregion
235  
236 #region Private Methods
237  
238 private void RemoveDirectories(string rootDir, string[] dirNames)
239 {
240 foreach(string dir in Directory.GetDirectories(rootDir))
241 {
242 string simpleName = Path.GetFileName(dir);
243  
244 if(Array.IndexOf(dirNames, simpleName) != -1)
245 {
246 //delete if the name matches one of the directory names to delete
247 string fullDirPath = Path.GetFullPath(dir);
248 Directory.Delete(fullDirPath,true);
249 }
250 else//not a match, so check children
251 {
252 RemoveDirectories(dir,dirNames);
253 //recurse, checking children for them
254 }
255 }
256 }
257  
258 // private void RemoveDirectoryMatches(string rootDir, string dirPattern)
259 // {
260 // foreach(string dir in Directory.GetDirectories(rootDir))
261 // {
262 // foreach(string match in Directory.GetDirectories(dir))
263 // {//delete all child directories that match
264 // Directory.Delete(Path.GetFullPath(match),true);
265 // }
266 // //recure through the rest checking for nested matches to delete
267 // RemoveDirectoryMatches(dir,dirPattern);
268 // }
269 // }
270  
271 private void LoadSchema()
272 {
273 Assembly assembly = this.GetType().Assembly;
274 Stream stream = assembly.GetManifestResourceStream("Prebuild.data." + m_Schema);
275 if(stream == null)
276 {
277 //try without the default namespace prepending to it in case was compiled with SharpDevelop or MonoDevelop instead of Visual Studio .NET
278 stream = assembly.GetManifestResourceStream(m_Schema);
279 if(stream == null)
280 {
281 throw new System.Reflection.TargetException(string.Format("Could not find the scheme embedded resource file '{0}'.", m_Schema));
282 }
283 }
284 XmlReader schema = new XmlTextReader(stream);
285  
286 m_Schemas = new XmlSchemaCollection();
287 m_Schemas.Add(m_SchemaURI, schema);
288 }
289  
290 private void CacheVersion()
291 {
292 m_Version = Assembly.GetEntryAssembly().GetName().Version;
293 }
294  
295 private void CacheTargets(Assembly assm)
296 {
297 foreach(Type t in assm.GetTypes())
298 {
299 TargetAttribute ta = (TargetAttribute)Helper.CheckType(t, typeof(TargetAttribute), typeof(ITarget));
300  
301 if(ta == null)
302 continue;
303  
304 if (t.IsAbstract)
305 continue;
306  
307 ITarget target = (ITarget)assm.CreateInstance(t.FullName);
308 if (target == null)
309 {
310 throw new MissingMethodException("Could not create ITarget instance");
311 }
312  
313 m_Targets[ta.Name] = target;
314 }
315 }
316  
317 private void CacheNodeTypes(Assembly assm)
318 {
319 foreach(Type t in assm.GetTypes())
320 {
321 foreach (DataNodeAttribute dna in t.GetCustomAttributes(typeof(DataNodeAttribute), true))
322 {
323 NodeEntry ne = new NodeEntry();
324 ne.Type = t;
325 ne.Attribute = dna;
326 m_Nodes[dna.Name] = ne;
327 }
328 }
329 }
330  
331 private void LogBanner()
332 {
333 m_Log.Write("Prebuild v" + this.Version);
334 m_Log.Write("Copyright (c) 2004-2008");
335 m_Log.Write("Matthew Holmes (matthew@wildfiregames.com),");
336 m_Log.Write("Dan Moorehead (dan05a@gmail.com),");
337 m_Log.Write("David Hudson (jendave@yahoo.com),");
338 m_Log.Write("Rob Loach (http://www.robloach.net),");
339 m_Log.Write("C.J. Adams-Collier (cjac@colliertech.org),");
340  
341 m_Log.Write("See 'prebuild /usage' for help");
342 m_Log.Write();
343 }
344  
345  
346  
347 private void ProcessFile(string file)
348 {
349 ProcessFile(file, this.m_Solutions);
350 }
351  
352 public void ProcessFile(ProcessNode node, SolutionNode parent)
353 {
354 if (node.IsValid)
355 {
356 ArrayList list = new ArrayList();
357 ProcessFile(node.Path, list);
358  
359 foreach (SolutionNode solution in list)
360 parent.SolutionsTable[solution.Name] = solution;
361 }
362 }
363  
364 /// <summary>
365 ///
366 /// </summary>
367 /// <param name="file"></param>
368 /// <returns></returns>
369 public void ProcessFile(string file, IList solutions)
370 {
371 m_CurrentWorkingDirectory.Push();
372  
373 string path = file;
374 try
375 {
376 try
377 {
378 path = Helper.ResolvePath(path);
379 }
380 catch(ArgumentException)
381 {
382 m_Log.Write("Could not open Prebuild file: " + path);
383 m_CurrentWorkingDirectory.Pop();
384 return;
385 }
386  
387 m_CurrentFile = path;
388 Helper.SetCurrentDir(Path.GetDirectoryName(path));
389  
390 XmlTextReader reader = new XmlTextReader(path);
391  
392 Core.Parse.Preprocessor pre = new Core.Parse.Preprocessor();
393  
394 //register command line arguments as XML variables
395 IDictionaryEnumerator dict = m_CommandLine.GetEnumerator();
396 while (dict.MoveNext())
397 {
398 string name = dict.Key.ToString().Trim();
399 if (name.Length > 0)
400 pre.RegisterVariable(name, dict.Value.ToString());
401 }
402  
403 string xml = pre.Process(reader);//remove script and evaulate pre-proccessing to get schema-conforming XML
404  
405 // See if the user put into a pseudo target of "prebuild:preprocessed-input" to indicate they want to see the
406 // output before the system processes it.
407 if (m_CommandLine.WasPassed("ppi"))
408 {
409 // Get the filename if there is one, otherwise use a default.
410 string ppiFile = m_CommandLine["ppi"];
411 if (ppiFile == null || ppiFile.Trim().Length == 0)
412 {
413 ppiFile = "preprocessed-input.xml";
414 }
415  
416 // Write out the string to the given stream.
417 try
418 {
419 using (StreamWriter ppiWriter = new StreamWriter(ppiFile))
420 {
421 ppiWriter.WriteLine(xml);
422 }
423 }
424 catch(IOException ex)
425 {
426 Console.WriteLine("Could not write PPI file '{0}': {1}", ppiFile, ex.Message);
427 }
428  
429 // Finish processing this special tag.
430 return;
431 }
432  
433 m_CurrentDoc = new XmlDocument();
434 try
435 {
436 XmlValidatingReader validator = new XmlValidatingReader(new XmlTextReader(new StringReader(xml)));
437  
438 //validate while reading from string into XmlDocument DOM structure in memory
439 foreach(XmlSchema schema in m_Schemas)
440 {
441 validator.Schemas.Add(schema);
442 }
443 m_CurrentDoc.Load(validator);
444 }
445 catch(XmlException e)
446 {
447 throw new XmlException(e.ToString());
448 }
449  
450 //is there a purpose to writing it? An syntax/schema problem would have been found during pre.Process() and reported with details
451 if(m_CommandLine.WasPassed("ppo"))
452 {
453 string ppoFile = m_CommandLine["ppo"];
454 if(ppoFile == null || ppoFile.Trim().Length < 1)
455 {
456 ppoFile = "preprocessed.xml";
457 }
458  
459 StreamWriter writer = null;
460 try
461 {
462 writer = new StreamWriter(ppoFile);
463 writer.Write(xml);
464 }
465 catch(IOException ex)
466 {
467 Console.WriteLine("Could not write PPO file '{0}': {1}", ppoFile, ex.Message);
468 }
469 finally
470 {
471 if(writer != null)
472 {
473 writer.Close();
474 }
475 }
476 return;
477 }
478 //start reading the xml config file
479 XmlElement rootNode = m_CurrentDoc.DocumentElement;
480 //string suggestedVersion = Helper.AttributeValue(rootNode,"version","1.0");
481 Helper.CheckForOSVariables = Helper.ParseBoolean(rootNode,"checkOsVars",false);
482  
483 foreach(XmlNode node in rootNode.ChildNodes)//solutions or if pre-proc instructions
484 {
485 IDataNode dataNode = ParseNode(node, null);
486 if(dataNode is ProcessNode)
487 {
488 ProcessNode proc = (ProcessNode)dataNode;
489 if(proc.IsValid)
490 {
491 ProcessFile(proc.Path);
492 }
493 }
494 else if(dataNode is SolutionNode)
495 {
496 solutions.Add(dataNode);
497 }
498 }
499 }
500 catch(XmlSchemaException xse)
501 {
502 m_Log.Write("XML validation error at line {0} in {1}:\n\n{2}",
503 xse.LineNumber, path, xse.Message);
504 }
505 finally
506 {
507 m_CurrentWorkingDirectory.Pop();
508 }
509 }
510  
511 #endregion
512  
513 #region Public Methods
514  
515 /// <summary>
516 /// Allows the project.
517 /// </summary>
518 /// <param name="projectGroupsFlags">The project groups flags.</param>
519 /// <returns></returns>
520 public bool AllowProject(string projectGroupsFlags)
521 {
522 if(m_ProjectGroups != null && m_ProjectGroups.Length > 0)
523 {
524 if(projectGroupsFlags != null && projectGroupsFlags.Length == 0)
525 {
526 foreach(string group in projectGroupsFlags.Split('|'))
527 {
528 if(Array.IndexOf(m_ProjectGroups, group) != -1) //if included in the filter list
529 {
530 return true;
531 }
532 }
533 }
534 return false;//not included in the list or no groups specified for the project
535 }
536 return true;//no filter specified in the command line args
537 }
538  
539 /// <summary>
540 /// Gets the type of the node.
541 /// </summary>
542 /// <param name="node">The node.</param>
543 /// <returns></returns>
544 public Type GetNodeType(XmlNode node)
545 {
546 if( node == null )
547 {
548 throw new ArgumentNullException("node");
549 }
550 if(!m_Nodes.ContainsKey(node.Name))
551 {
552 return null;
553 }
554  
555 NodeEntry ne = (NodeEntry)m_Nodes[node.Name];
556 return ne.Type;
557 }
558  
559 /// <summary>
560 ///
561 /// </summary>
562 /// <param name="node"></param>
563 /// <param name="parent"></param>
564 /// <returns></returns>
565 public IDataNode ParseNode(XmlNode node, IDataNode parent)
566 {
567 return ParseNode(node, parent, null);
568 }
569  
570 //Create an instance of the data node type that is mapped to the name of the xml DOM node
571 /// <summary>
572 /// Parses the node.
573 /// </summary>
574 /// <param name="node">The node.</param>
575 /// <param name="parent">The parent.</param>
576 /// <param name="preNode">The pre node.</param>
577 /// <returns></returns>
578 public IDataNode ParseNode(XmlNode node, IDataNode parent, IDataNode preNode)
579 {
580 IDataNode dataNode = null;
581  
582 try
583 {
584 if( node == null )
585 {
586 throw new ArgumentNullException("node");
587 }
588 if(preNode == null)
589 {
590 if(!m_Nodes.ContainsKey(node.Name))
591 {
592 //throw new XmlException("Unknown XML node: " + node.Name);
593 return null;
594 }
595  
596 NodeEntry ne = (NodeEntry)m_Nodes[node.Name];
597 Type type = ne.Type;
598 //DataNodeAttribute dna = ne.Attribute;
599  
600 dataNode = (IDataNode)type.Assembly.CreateInstance(type.FullName);
601 if(dataNode == null)
602 {
603 throw new System.Reflection.TargetException("Could not create new parser instance: " + type.FullName);
604 }
605 }
606 else
607 dataNode = preNode;
608  
609 dataNode.Parent = parent;
610 dataNode.Parse(node);
611 }
612 catch(WarningException wex)
613 {
614 m_Log.Write(LogType.Warning, wex.Message);
615 return null;
616 }
617 catch(FatalException fex)
618 {
619 m_Log.WriteException(LogType.Error, fex);
620 throw;
621 }
622 catch(Exception ex)
623 {
624 m_Log.WriteException(LogType.Error, ex);
625 throw;
626 }
627  
628 return dataNode;
629 }
630  
631 /// <summary>
632 /// Initializes the specified target.
633 /// </summary>
634 /// <param name="target">The target.</param>
635 /// <param name="args">The args.</param>
636 public void Initialize(LogTargets target, string[] args)
637 {
638 m_Targets = new Hashtable();
639 CacheTargets(this.GetType().Assembly);
640 m_Nodes = new Hashtable();
641 CacheNodeTypes(this.GetType().Assembly);
642 CacheVersion();
643  
644 m_CommandLine = new CommandLineCollection(args);
645  
646 string logFile = null;
647 if(m_CommandLine.WasPassed("log"))
648 {
649 logFile = m_CommandLine["log"];
650  
651 if(logFile != null && logFile.Length == 0)
652 {
653 logFile = "Prebuild.log";
654 }
655 }
656 else
657 {
658 target = target & ~LogTargets.File; //dont output to a file
659 }
660  
661 m_Log = new Log(target, logFile);
662 LogBanner();
663  
664 m_CurrentWorkingDirectory = new CurrentDirectory();
665  
666 m_Target = m_CommandLine["target"];
667 m_Clean = m_CommandLine["clean"];
668 string removeDirs = m_CommandLine["removedir"];
669 if(removeDirs != null && removeDirs.Length == 0)
670 {
671 m_RemoveDirectories = removeDirs.Split('|');
672 }
673  
674 string flags = m_CommandLine["allowedgroups"];//allows filtering by specifying a pipe-delimited list of groups to include
675 if(flags != null && flags.Length == 0)
676 {
677 m_ProjectGroups = flags.Split('|');
678 }
679 m_PauseAfterFinish = m_CommandLine.WasPassed("pause");
680  
681 LoadSchema();
682  
683 m_Solutions = new ArrayList();
684 m_Refs = new StringCollection();
685 }
686  
687 /// <summary>
688 /// Processes this instance.
689 /// </summary>
690 public void Process()
691 {
692 bool perfomedOtherTask = false;
693 if(m_RemoveDirectories != null && m_RemoveDirectories.Length > 0)
694 {
695 try
696 {
697 RemoveDirectories(".",m_RemoveDirectories);
698 }
699 catch(IOException e)
700 {
701 m_Log.Write("Failed to remove directories named {0}",m_RemoveDirectories);
702 m_Log.WriteException(LogType.Error,e);
703 }
704 catch(UnauthorizedAccessException e)
705 {
706 m_Log.Write("Failed to remove directories named {0}",m_RemoveDirectories);
707 m_Log.WriteException(LogType.Error,e);
708 }
709 perfomedOtherTask = true;
710 }
711  
712 if(m_Target != null && m_Clean != null)
713 {
714 m_Log.Write(LogType.Error, "The options /target and /clean cannot be passed together");
715 return;
716 }
717 else if(m_Target == null && m_Clean == null)
718 {
719 if(perfomedOtherTask) //finished
720 {
721 return;
722 }
723 m_Log.Write(LogType.Error, "Must pass either /target or /clean to process a Prebuild file");
724 return;
725 }
726  
727 string file = "./prebuild.xml";
728 if(m_CommandLine.WasPassed("file"))
729 {
730 file = m_CommandLine["file"];
731 }
732  
733 ProcessFile(file);
734  
735 string target = (m_Target != null ? m_Target.ToLower() : m_Clean.ToLower());
736 bool clean = (m_Target == null);
737 if(clean && target != null && target.Length == 0)
738 {
739 target = "all";
740 }
741 if(clean && target == "all")//default to all if no target was specified for clean
742 {
743 //check if they passed yes
744 if (!m_CommandLine.WasPassed("yes"))
745 {
746 Console.WriteLine("WARNING: This operation will clean ALL project files for all targets, are you sure? (y/n):");
747 string ret = Console.ReadLine();
748 if(ret == null)
749 {
750 return;
751 }
752 ret = ret.Trim().ToLower();
753 if((ret.ToLower() != "y" && ret.ToLower() != "yes"))
754 {
755 return;
756 }
757 }
758 //clean all targets (just cleaning vs2002 target didn't clean nant)
759 foreach(ITarget targ in m_Targets.Values)
760 {
761 targ.Clean(this);
762 }
763 }
764 else
765 {
766 if (!m_Targets.Contains(target)) {
767 m_Log.Write(LogType.Error, "Unknown Target \"{0}\"", target);
768 return;
769 }
770 ITarget targ = (ITarget)m_Targets[target];
771  
772 if(clean)
773 {
774 targ.Clean(this);
775 }
776 else
777 {
778 targ.Write(this);
779 }
780 }
781  
782 m_Log.Flush();
783 }
784  
785 #endregion
786  
787 #region IDisposable Members
788  
789 /// <summary>
790 ///
791 /// </summary>
792 public void Dispose()
793 {
794 Dispose(true);
795 GC.SuppressFinalize(this);
796 }
797  
798 /// <summary>
799 /// Dispose objects
800 /// </summary>
801 /// <param name="disposing">
802 /// If true, it will dispose close the handle
803 /// </param>
804 /// <remarks>
805 /// Will dispose managed and unmanaged resources.
806 /// </remarks>
807 protected virtual void Dispose(bool disposing)
808 {
809 if (!this.disposed)
810 {
811 if (disposing)
812 {
813 if (this.m_Log != null)
814 {
815 this.m_Log.Close();
816 this.m_Log = null;
817 }
818 }
819 }
820 this.disposed = true;
821 }
822  
823 /// <summary>
824 ///
825 /// </summary>
826 ~Kernel()
827 {
828 this.Dispose(false);
829 }
830  
831 /// <summary>
832 /// Closes and destroys this object
833 /// </summary>
834 /// <remarks>
835 /// Same as Dispose(true)
836 /// </remarks>
837 public void Close()
838 {
839 Dispose();
840 }
841  
842 #endregion
843 }
844 }