* TypeDescriptor.cs: Fix GetComponentName() so that it returns the type
[mono.git] / mcs / nant / src / Project.cs
1 // NAnt - A .NET build tool\r
2 // Copyright (C) 2001 Gerry Shaw\r
3 //\r
4 // This program is free software; you can redistribute it and/or modify\r
5 // it under the terms of the GNU General Public License as published by\r
6 // the Free Software Foundation; either version 2 of the License, or\r
7 // (at your option) any later version.\r
8 //\r
9 // This program is distributed in the hope that it will be useful,\r
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
12 // GNU General Public License for more details.\r
13 //\r
14 // You should have received a copy of the GNU General Public License\r
15 // along with this program; if not, write to the Free Software\r
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
17 //\r
18 // Gerry Shaw (gerry_shaw@yahoo.com)\r
19 // Ian MacLean (ian_maclean@another.com)\r
20 \r
21 namespace SourceForge.NAnt {\r
22 \r
23     using System;\r
24     using System.IO;\r
25     using System.Reflection;\r
26     using System.Text.RegularExpressions;\r
27     using System.Xml;\r
28     using System.Xml.XPath;\r
29     using System.Collections;\r
30     using System.Collections.Specialized;\r
31 \r
32     /// <summary>\r
33     /// Central representation of an NAnt project.\r
34     /// </summary>\r
35     public class Project {\r
36 \r
37         public static readonly string BuildFilePattern = "*.build";\r
38 \r
39         /// <summary>\r
40         /// Finds the file name for the build file in the specified directory.\r
41         /// </summary>\r
42         /// <param name="directory">The directory to look for a build file.  When in doubt use Environment.CurrentDirectory for directory.</param>\r
43         /// <returns>The path to the build file or <c>null</c> if no build file could be found.</returns>\r
44         public static string FindBuildFileName(string directory) {\r
45             string buildFileName = null;\r
46 \r
47             // find first file ending in .build\r
48             DirectoryInfo directoryInfo = new DirectoryInfo(directory);\r
49             FileInfo[] files = directoryInfo.GetFiles(BuildFilePattern);\r
50             if (files.Length > 0) {\r
51                 buildFileName = Path.Combine(directory, files[0].Name);\r
52             }\r
53             return buildFileName;\r
54         }\r
55 \r
56         string _name;\r
57         string _defaultTargetName;\r
58         string _baseDirectory;\r
59         string _buildFileName;\r
60         bool _verbose = false;\r
61 \r
62         StringCollection _buildTargets = new StringCollection();\r
63         TaskCollection _tasks = new TaskCollection();\r
64         TargetCollection _targets = new TargetCollection();\r
65         XPathTextPositionMap _positionMap; // created when Xml document is loaded\r
66         TaskFactory _taskFactory; // created in constructor\r
67         PropertyDictionary _properties = new PropertyDictionary();\r
68 \r
69         public Project() {\r
70             _taskFactory = new TaskFactory(this);\r
71         }\r
72 \r
73         /// <summary>\r
74         /// The name of the project.\r
75         /// </summary>\r
76         public string Name {\r
77             get { return _name; }\r
78             set { _name = value; }\r
79         }\r
80 \r
81         public string BaseDirectory {\r
82             get { return _baseDirectory; }\r
83             set { _baseDirectory = value; }\r
84         }\r
85 \r
86         public string BuildFileName {\r
87             get { return _buildFileName; }\r
88             set { _buildFileName = value; }\r
89         }\r
90 \r
91         /// <summary>\r
92         /// When true tasks should output more output.\r
93         /// </summary>\r
94         public bool Verbose {\r
95             get { return _verbose; }\r
96             set { _verbose = value; }\r
97         }\r
98 \r
99         /// <summary>\r
100         /// The list of targets to built.\r
101         /// </summary>\r
102         /// <remarks>\r
103         /// Targets are built in the order they appear in the collection.  If\r
104         /// the collection is empty the default target will be built.\r
105         /// </remarks>\r
106         public StringCollection BuildTargets {\r
107             get { return _buildTargets; }\r
108         }\r
109 \r
110         /// <summary>\r
111         /// The list of tasks to perform before any targets executed.\r
112         /// </summary>\r
113         /// <remarks>\r
114         /// Tasks are executed in the order they appear in the collection.\r
115         /// </remarks>\r
116         public TaskCollection Tasks {\r
117             get { return _tasks; }\r
118         }\r
119 \r
120         public PropertyDictionary Properties {\r
121             get { return _properties; }\r
122         }\r
123 \r
124         public TargetCollection Targets {\r
125             get { return _targets; }\r
126         }\r
127 \r
128         public bool Run() {\r
129             bool buildResult = false;\r
130             try {\r
131                 DateTime startTime = DateTime.Now;\r
132 \r
133                 if (BaseDirectory == null) {\r
134                     BaseDirectory = Environment.CurrentDirectory;\r
135                 }\r
136                 BaseDirectory = Path.GetFullPath(BaseDirectory);\r
137 \r
138                 if (BuildFileName == null || BuildFileName == String.Empty) {\r
139                     BuildFileName = FindBuildFileName(BaseDirectory);\r
140                     if (BuildFileName == null) {\r
141                         throw new BuildException(String.Format("Could not find a '{0}' file in '{1}'", BuildFilePattern, BaseDirectory));\r
142                     }\r
143                 }\r
144 \r
145                 Log.WriteLine("Buildfile: {0}", BuildFileName);\r
146                 if (Verbose) {\r
147                     Log.WriteLine("Base Directory: {0}", BaseDirectory);\r
148                 }\r
149 \r
150                 XmlDocument doc = new XmlDocument();\r
151                 try {\r
152                     doc.Load(BuildFileName);\r
153                     // TODO: validate against xsd schema\r
154                 } catch (XmlException e) {\r
155                     throw new BuildException(String.Format("Could not load '{0}'", BuildFileName), e);\r
156                 }\r
157 \r
158                 Initialize(doc);\r
159                 Properties.Add("nant.buildfile", BuildFileName);\r
160 \r
161                 Execute();\r
162 \r
163                 Log.WriteLine();\r
164                 Log.WriteLine("BUILD SUCCEEDED");\r
165 \r
166                 TimeSpan buildTime = DateTime.Now - startTime;\r
167                 Log.WriteLine();\r
168                 Log.WriteLine("Total time: {0} seconds", (int) buildTime.TotalSeconds);\r
169 \r
170                 buildResult = true;\r
171             } catch (BuildException e) {\r
172                 Log.WriteLine();\r
173                 Log.WriteLine("BUILD FAILED");\r
174                 Log.WriteLine(e.Message);\r
175                 if (e.InnerException != null) {\r
176                     Log.WriteLine(e.InnerException.Message);\r
177                 }\r
178             } catch (Exception e) {\r
179                 // all other exceptions should have been caught\r
180                 Log.WriteLine();\r
181                 Log.WriteLine("INTERNAL ERROR");\r
182                 Log.WriteLine(e.ToString());\r
183             }\r
184             return buildResult;\r
185         }\r
186 \r
187         public int AddTasks(string assemblyPath) {\r
188 \r
189             Assembly assembly;\r
190             if (assemblyPath == null) {\r
191                 assembly = Assembly.GetExecutingAssembly();\r
192             } else {\r
193                 assembly = Assembly.LoadFrom(assemblyPath);\r
194             }\r
195 \r
196             int taskCount = 0;\r
197             foreach(Type type in assembly.GetTypes()) {\r
198                 if (type.IsSubclassOf(typeof(Task)) && !type.IsAbstract) {\r
199                     if (_taskFactory.Builders.Add(new TaskBuilder(type.FullName, assemblyPath))) {\r
200                         taskCount++;\r
201                     }\r
202                 }\r
203             }\r
204             return taskCount;\r
205         }\r
206 \r
207         public void Initialize(XmlDocument doc) {\r
208 \r
209             Name = doc.SelectSingleNode("project/@name").Value;\r
210 \r
211             // make it possible for user to override this value\r
212             if (BaseDirectory == null) {\r
213                 BaseDirectory = doc.SelectSingleNode("project/@basedir").Value;\r
214             }\r
215 \r
216             // used only if BuildTargets collection is empty\r
217             _defaultTargetName = doc.SelectSingleNode("project/@default").Value;\r
218 \r
219             // initialize builtin tasks\r
220             AddTasks(null);\r
221 \r
222             // init static built in properties\r
223             Properties.Add("nant.project.name", Name);\r
224             Properties.Add("nant.base.dir",     BaseDirectory);\r
225             Properties.Add("nant.default.name", _defaultTargetName);\r
226 \r
227             // add all environment variables\r
228             IDictionary variables = Environment.GetEnvironmentVariables();\r
229             foreach (string name in variables.Keys) {\r
230                 string value = (string) variables[name];\r
231                 Properties.Add("nant.env." + name, value);\r
232             }\r
233 \r
234             // Load line Xpath to linenumber array\r
235             _positionMap = new XPathTextPositionMap(doc.BaseURI);\r
236 \r
237             // process all the non-target nodes (these are global tasks for the project)\r
238             XmlNodeList taskList = doc.SelectNodes("project/*[name() != 'target']");\r
239             foreach (XmlNode taskNode in taskList) {\r
240 \r
241                 // TODO: do somethiing like Project.CreateTask(taskNode) and have the project set the location\r
242                 TextPosition textPosition = _positionMap.GetTextPosition(taskNode);\r
243 \r
244                 Task task = CreateTask(taskNode);\r
245                 if (task != null) {\r
246                     Tasks.Add(task);\r
247                 }\r
248             }\r
249 \r
250             // execute global tasks now - before anything else\r
251             // this lets us include tasks that do things like add more tasks\r
252             foreach (Task task in Tasks) {\r
253                 task.Execute();\r
254             }\r
255 \r
256             // process all the targets\r
257             XmlNodeList targetList = doc.SelectNodes("project/target");\r
258             foreach (XmlNode targetNode in targetList) {\r
259                 Target target = new Target(this);\r
260                 target.Initialize(targetNode);\r
261                 Targets.Add(target);\r
262             }\r
263         }\r
264 \r
265         public void Execute() {\r
266             if (BuildTargets.Count == 0) {\r
267                 BuildTargets.Add(_defaultTargetName);\r
268             }\r
269 \r
270             foreach(string targetName in BuildTargets) {\r
271                 Execute(targetName);\r
272             }\r
273         }\r
274 \r
275         public void Execute(string targetName) {\r
276             Target target = Targets.Find(targetName);\r
277             if (target == null) {\r
278                 throw new BuildException(String.Format("unknown target '{0}'", targetName));\r
279             }\r
280             target.Execute();\r
281         }\r
282 \r
283         public Task CreateTask(XmlNode taskNode) {\r
284             return CreateTask(taskNode, null);\r
285         }\r
286 \r
287         public Task CreateTask(XmlNode taskNode, Target target) {\r
288             Task task = _taskFactory.CreateTask(taskNode, target);\r
289             if (task != null) {\r
290                 // save task location in case of error\r
291                 TextPosition pos = _positionMap.GetTextPosition(taskNode);\r
292 \r
293                 // initialize the task\r
294                 task.Initialize(taskNode, new Location(taskNode.BaseURI, pos.Line, pos.Column));\r
295             }\r
296             return task;\r
297         }\r
298 \r
299         public string ExpandText(string input) {\r
300             string output = input;\r
301             if (input != null) {\r
302                 const string pattern = @"\$\{([^\}]*)\}";\r
303                 foreach (Match m in Regex.Matches(input, pattern)) {\r
304                     if (m.Length > 0) {\r
305 \r
306                         string token         = m.ToString();\r
307                         string propertyName  = m.Groups[1].Captures[0].Value;\r
308                         string propertyValue = Properties[propertyName];\r
309 \r
310                         if (propertyValue != null) {\r
311                             output = output.Replace(token, propertyValue);\r
312                         }\r
313                     }\r
314                 }\r
315             }\r
316             return output;\r
317         }\r
318 \r
319         public string GetFullPath(string path) {\r
320             string baseDir = ExpandText(BaseDirectory);\r
321 \r
322             if (path != null) {\r
323                 if (!Path.IsPathRooted(path)) {\r
324                     path = Path.Combine(baseDir, path);\r
325                 }\r
326             } else {\r
327                 path = baseDir;\r
328             }\r
329             return Path.GetFullPath(path);\r
330         }\r
331     }\r
332 }\r