Merge from amd64-branch:
[reactos.git] / reactos / tools / rbuild / project.cpp
1 /*
2 * Copyright (C) 2005 Casper S. Hornstrup
3 * Copyright (C) 2008 Hervé Poussineau
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 #include "pch.h"
20 #include <assert.h>
21
22 #include "rbuild.h"
23 #include "backend/backend.h"
24
25 using std::string;
26 using std::vector;
27
28 /* static */ string
29 Environment::GetVariable ( const string& name )
30 {
31 char* value = getenv ( name.c_str () );
32 if ( value != NULL && strlen ( value ) > 0 )
33 return ssprintf ( "%s",
34 value );
35 else
36 return "";
37 }
38
39 string
40 Environment::GetArch ()
41 {
42 return GetEnvironmentVariablePathOrDefault ( "ROS_ARCH", "i386" );
43 }
44
45 /* static */ string
46 Environment::GetEnvironmentVariablePathOrDefault ( const string& name,
47 const string& defaultValue )
48 {
49 const string& environmentVariableValue = Environment::GetVariable ( name );
50 if ( environmentVariableValue.length () > 0 )
51 return NormalizeFilename ( environmentVariableValue );
52 else
53 return defaultValue;
54 }
55
56 /* static */ string
57 Environment::GetIntermediatePath ()
58 {
59 string defaultIntermediate =
60 string( "obj-" ) + GetArch ();
61 return GetEnvironmentVariablePathOrDefault ( "ROS_INTERMEDIATE",
62 defaultIntermediate );
63 }
64
65 /* static */ string
66 Environment::GetOutputPath ()
67 {
68 string defaultOutput =
69 string( "output-" ) + GetArch ();
70 return GetEnvironmentVariablePathOrDefault ( "ROS_OUTPUT",
71 defaultOutput );
72 }
73
74 /* static */ string
75 Environment::GetInstallPath ()
76 {
77 string defaultInstall = GetCdOutputPath ();
78 return GetEnvironmentVariablePathOrDefault ( "ROS_INSTALL",
79 defaultInstall );
80 }
81
82 /* static */ string
83 Environment::GetCdOutputPath ()
84 {
85 return GetEnvironmentVariablePathOrDefault ( "ROS_CDOUTPUT",
86 "reactos" );
87 }
88
89 /* static */ string
90 Environment::GetAutomakeFile ( const std::string& defaultFile )
91 {
92 return GetEnvironmentVariablePathOrDefault ( "ROS_AUTOMAKE",
93 defaultFile );
94 }
95
96 ParseContext::ParseContext ()
97 : compilationUnit (NULL)
98 {
99 }
100
101
102 FileLocation::FileLocation ( const DirectoryLocation directory,
103 const std::string& relative_path,
104 const std::string& name,
105 const XMLElement *node )
106 : directory ( directory ),
107 relative_path ( NormalizeFilename ( relative_path ) ),
108 name ( name )
109 {
110 if ( relative_path[0] == '/' ||
111 relative_path[0] == '\\' ||
112 relative_path.find ( '$' ) != string::npos ||
113 ( relative_path.length () > 1 && ( relative_path[1] == ':' ||
114 relative_path.find_last_of ( "/\\" ) == relative_path.length () - 1 ) ) ||
115 ( relative_path.length () > 3 && relative_path.find ( ':' ) != string::npos )
116 )
117 {
118 if ( node )
119 throw InvalidOperationException ( __FILE__,
120 __LINE__,
121 "Invalid relative path '%s' at %s",
122 relative_path.c_str (),
123 node->location.c_str () );
124 else
125 throw InvalidOperationException ( __FILE__,
126 __LINE__,
127 "Invalid relative path '%s'",
128 relative_path.c_str () );
129 }
130
131 if ( name.find_first_of ( "/\\:" ) != string::npos )
132 {
133 if ( node )
134 throw InvalidOperationException ( __FILE__,
135 __LINE__,
136 "Invalid file name '%s' at %s",
137 name.c_str (),
138 node->location.c_str () );
139 else
140 throw InvalidOperationException ( __FILE__,
141 __LINE__,
142 "Invalid file name '%s'",
143 name.c_str () );
144 }
145 }
146
147
148 FileLocation::FileLocation ( const FileLocation& other )
149 : directory ( other.directory ),
150 relative_path ( other.relative_path ),
151 name ( other.name )
152 {
153 }
154
155
156 Project::Project ( const Configuration& configuration,
157 const string& filename,
158 const std::map<std::string, std::string>* properties )
159 : xmlfile (filename),
160 node (NULL),
161 head (NULL),
162 configuration (configuration)
163 {
164 _backend = NULL;
165
166 if ( properties )
167 {
168 std::map<string, string>::const_iterator it;
169 for (it = properties->begin (); it != properties->end (); it++)
170 {
171 const Property *existing = LookupProperty( it->first );
172 if ( !existing )
173 {
174 Property* property = new Property ( *this, NULL, it->first, it->second );
175 non_if_data.properties.insert ( std::make_pair ( property->name, property ) );
176 }
177 }
178 }
179
180 ReadXml();
181 }
182
183 Project::~Project ()
184 {
185 size_t i;
186 if ( _backend )
187 delete _backend;
188 #ifdef NOT_NEEDED_SINCE_THESE_ARE_CLEANED_BY_IFABLE_DATA
189 for ( i = 0; i < modules.size (); i++ )
190 delete modules[i];
191 #endif
192 for ( i = 0; i < linkerFlags.size (); i++ )
193 delete linkerFlags[i];
194 for ( i = 0; i < cdfiles.size (); i++ )
195 delete cdfiles[i];
196 for ( i = 0; i < installfiles.size (); i++ )
197 delete installfiles[i];
198 if ( head )
199 delete head;
200 }
201
202 const Property*
203 Project::LookupProperty ( const string& name ) const
204 {
205 std::map<std::string, Property*>::const_iterator p = non_if_data.properties.find(name);
206
207 if ( p == non_if_data.properties.end () )
208 return NULL;
209
210 return p->second;
211 }
212
213 string
214 Project::ResolveNextProperty ( const string& s ) const
215 {
216 size_t i = s.find ( "${" );
217 if ( i == string::npos )
218 i = s.find ( "$(" );
219 if ( i != string::npos )
220 {
221 string endCharacter;
222 if ( s[i + 1] == '{' )
223 endCharacter = "}";
224 else
225 endCharacter = ")";
226 size_t j = s.find ( endCharacter );
227 if ( j != string::npos )
228 {
229 int propertyNameLength = j - i - 2;
230 string propertyName = s.substr ( i + 2, propertyNameLength );
231 const Property* property = LookupProperty ( propertyName );
232 if ( property != NULL )
233 return string ( s ).replace ( i, propertyNameLength + 3, property->value );
234 }
235 }
236 return s;
237 }
238
239 string
240 Project::ResolveProperties ( const string& s ) const
241 {
242 string s2 = s;
243 string s3;
244 do
245 {
246 s3 = s2;
247 s2 = ResolveNextProperty ( s3 );
248 } while ( s2 != s3 );
249 return s2;
250 }
251
252 void
253 Project::ExecuteInvocations ()
254 {
255 for( std::map<std::string, Module*>::const_iterator p = modules.begin(); p != modules.end(); ++ p )
256 p->second->InvokeModule ();
257 }
258
259 void
260 Project::ReadXml ()
261 {
262 Path path;
263 head = XMLLoadFile ( xmlfile, path, xmlbuildfiles );
264 node = NULL;
265 for ( size_t i = 0; i < head->subElements.size (); i++ )
266 {
267 if ( head->subElements[i]->name == "project" )
268 {
269 node = head->subElements[i];
270 string path;
271 ProcessXML ( path );
272 return;
273 }
274 }
275
276 if (node == NULL)
277 node = head->subElements[0];
278
279 throw XMLInvalidBuildFileException (
280 node->location,
281 "Document contains no 'project' tag." );
282 }
283
284 void
285 Project::ProcessXML ( const string& path )
286 {
287 const XMLAttribute *att;
288 if ( node->name != "project" )
289 throw Exception ( "internal tool error: Project::ProcessXML() called with non-<project> node" );
290
291 att = node->GetAttribute ( "name", false );
292 if ( !att )
293 name = "Unnamed";
294 else
295 name = att->value;
296
297 att = node->GetAttribute ( "makefile", true );
298 assert(att);
299 makefile = Environment::GetAutomakeFile ( att->value );
300
301 att = node->GetAttribute ( "allowwarnings", false );
302 allowWarningsSet = att != NULL;
303 if ( att != NULL )
304 allowWarnings = att->value == "true";
305
306 size_t i;
307 for ( i = 0; i < node->subElements.size (); i++ )
308 {
309 ParseContext parseContext;
310 ProcessXMLSubElement ( *node->subElements[i], path, parseContext );
311 }
312
313 non_if_data.ProcessXML ();
314 host_non_if_data.ProcessXML ();
315
316 non_if_data.ExtractModules( modules );
317
318 for ( i = 0; i < linkerFlags.size (); i++ )
319 linkerFlags[i]->ProcessXML ();
320 for( std::map<std::string, Module*>::const_iterator p = modules.begin(); p != modules.end(); ++ p )
321 p->second->ProcessXML ();
322 for ( i = 0; i < cdfiles.size (); i++ )
323 cdfiles[i]->ProcessXML ();
324 for ( i = 0; i < installfiles.size (); i++ )
325 installfiles[i]->ProcessXML ();
326 }
327
328 void
329 Project::ProcessXMLSubElement ( const XMLElement& e,
330 const string& path,
331 ParseContext& parseContext )
332 {
333 const XMLAttribute* att;
334
335 att = e.GetAttribute ( "compilerset", false );
336
337 if ( att )
338 {
339 CompilerSet compilerSet;
340
341 if ( att->value == "msc" )
342 compilerSet = MicrosoftC;
343 else if ( att->value == "gcc" )
344 compilerSet = GnuGcc;
345 else
346 throw InvalidAttributeValueException (
347 e.location,
348 "compilerset",
349 att->value );
350
351 if ( compilerSet != configuration.Compiler )
352 return;
353 }
354
355 att = e.GetAttribute ( "linkerset", false );
356
357 if ( att )
358 {
359 LinkerSet linkerSet;
360
361 if ( att->value == "mslink" )
362 linkerSet = MicrosoftLink;
363 else if ( att->value == "ld" )
364 linkerSet = GnuLd;
365 else
366 throw InvalidAttributeValueException (
367 e.location,
368 "linkerset",
369 att->value );
370
371 if ( linkerSet != configuration.Linker )
372 return;
373 }
374
375 bool subs_invalid = false;
376
377 string subpath(path);
378 if ( e.name == "module" )
379 {
380 Module* module = new Module ( *this, e, path );
381 if ( LocateModule ( module->name ) )
382 throw XMLInvalidBuildFileException (
383 node->location,
384 "module name conflict: '%s' (originally defined at %s)",
385 module->name.c_str(),
386 module->node.location.c_str() );
387 non_if_data.modules.push_back ( module );
388 return; // defer processing until later
389 }
390 else if ( e.name == "cdfile" )
391 {
392 CDFile* cdfile = new CDFile ( *this, e, path );
393 cdfiles.push_back ( cdfile );
394 subs_invalid = true;
395 }
396 else if ( e.name == "installfile" )
397 {
398 InstallFile* installfile = new InstallFile ( *this, e, path );
399 installfiles.push_back ( installfile );
400 subs_invalid = true;
401 }
402 else if ( e.name == "directory" )
403 {
404 const XMLAttribute* att = e.GetAttribute ( "name", true );
405 assert(att);
406 subpath = GetSubPath ( *this, e.location, path, att->value );
407 }
408 else if ( e.name == "include" )
409 {
410 const XMLAttribute* host = e.GetAttribute("host", false);
411 Include* include = new Include ( *this, &e );
412
413 if(host && host->value == "true")
414 host_non_if_data.includes.push_back(include);
415 else
416 non_if_data.includes.push_back ( include );
417
418 subs_invalid = true;
419 }
420 else if ( e.name == "define" || e.name == "redefine" )
421 {
422 const XMLAttribute* host = e.GetAttribute("host", false);
423 Define* define = new Define ( *this, e );
424
425 if(host && host->value == "true")
426 host_non_if_data.defines.push_back(define);
427 else
428 non_if_data.defines.push_back ( define );
429
430 subs_invalid = true;
431 }
432 else if ( e.name == "compilerflag" )
433 {
434 CompilerFlag* pCompilerFlag = new CompilerFlag ( *this, e );
435 non_if_data.compilerFlags.push_back ( pCompilerFlag );
436 subs_invalid = true;
437 }
438 else if ( e.name == "linkerflag" )
439 {
440 linkerFlags.push_back ( new LinkerFlag ( *this, e ) );
441 subs_invalid = true;
442 }
443 else if ( e.name == "if" || e.name == "ifnot" )
444 {
445 const XMLAttribute* name;
446 name = e.GetAttribute ( "property", true );
447 assert( name );
448 const Property *property = LookupProperty( name->value );
449 const string *PropertyValue;
450 const string EmptyString;
451
452 if (property)
453 {
454 PropertyValue = &property->value;
455 }
456 else
457 {
458 // Property does not exist, treat it as being empty
459 PropertyValue = &EmptyString;
460 }
461
462 const XMLAttribute* value;
463 value = e.GetAttribute ( "value", true );
464 assert( value );
465
466 bool negate = ( e.name == "ifnot" );
467 bool equality = ( *PropertyValue == value->value );
468 if ( equality == negate )
469 {
470 // Failed, skip this element
471 if ( configuration.Verbose )
472 printf("Skipping 'If' at %s\n", e.location.c_str () );
473 return;
474 }
475 subs_invalid = false;
476 }
477 else if ( e.name == "property" )
478 {
479 Property* property = new Property ( e, *this, NULL );
480 non_if_data.properties.insert ( std::make_pair ( property->name, property ) );
481 }
482 if ( subs_invalid && e.subElements.size() )
483 {
484 throw XMLInvalidBuildFileException (
485 e.location,
486 "<%s> cannot have sub-elements",
487 e.name.c_str() );
488 }
489 for ( size_t i = 0; i < e.subElements.size (); i++ )
490 ProcessXMLSubElement ( *e.subElements[i], subpath, parseContext );
491 }
492
493 Module*
494 Project::LocateModule ( const string& name )
495 {
496 std::map<std::string, Module*>::const_iterator p = modules.find(name);
497
498 if ( p == modules.end() )
499 return NULL;
500
501 return p->second;
502 }
503
504 const Module*
505 Project::LocateModule ( const string& name ) const
506 {
507 std::map<std::string, Module*>::const_iterator p = modules.find(name);
508
509 if ( p == modules.end() )
510 return NULL;
511
512 return p->second;
513 }
514
515 const std::string&
516 Project::GetProjectFilename () const
517 {
518 return xmlfile;
519 }
520
521 std::string
522 Project::GetCompilerSet () const
523 {
524 switch ( configuration.Compiler )
525 {
526 case GnuGcc: return "gcc";
527 case MicrosoftC: return "msc";
528 default: assert ( false );
529 }
530 }
531
532 std::string
533 Project::GetLinkerSet () const
534 {
535 switch ( configuration.Linker )
536 {
537 case GnuLd: return "ld";
538 case MicrosoftLink: return "mslink";
539 default: assert ( false );
540 }
541 }