0592dc2604eeb3d2aca09158539ee1e04453707d
[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
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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.push_back (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 for ( size_t i = 0; i < non_if_data.properties.size (); i++ )
206 {
207 const Property* property = non_if_data.properties[i];
208 if ( property->name == name )
209 return property;
210 }
211 return NULL;
212 }
213
214 string
215 Project::ResolveNextProperty ( const string& s ) const
216 {
217 size_t i = s.find ( "${" );
218 if ( i == string::npos )
219 i = s.find ( "$(" );
220 if ( i != string::npos )
221 {
222 string endCharacter;
223 if ( s[i + 1] == '{' )
224 endCharacter = "}";
225 else
226 endCharacter = ")";
227 size_t j = s.find ( endCharacter );
228 if ( j != string::npos )
229 {
230 int propertyNameLength = j - i - 2;
231 string propertyName = s.substr ( i + 2, propertyNameLength );
232 const Property* property = LookupProperty ( propertyName );
233 if ( property != NULL )
234 return string ( s ).replace ( i, propertyNameLength + 3, property->value );
235 }
236 }
237 return s;
238 }
239
240 string
241 Project::ResolveProperties ( const string& s ) const
242 {
243 string s2 = s;
244 string s3;
245 do
246 {
247 s3 = s2;
248 s2 = ResolveNextProperty ( s3 );
249 } while ( s2 != s3 );
250 return s2;
251 }
252
253 void
254 Project::SetConfigurationOption ( char* s,
255 string name,
256 string alternativeName )
257 {
258 const Property* property = LookupProperty ( name );
259 if ( property != NULL && property->value.length () > 0 )
260 {
261 s = s + sprintf ( s,
262 "#define %s=%s\n",
263 property->name.c_str (),
264 property->value.c_str () );
265 }
266 else if ( property != NULL )
267 {
268 s = s + sprintf ( s,
269 "#define %s\n",
270 property->name.c_str () );
271 }
272 else if ( !alternativeName.empty() )
273 {
274 s = s + sprintf ( s,
275 "#define %s\n",
276 alternativeName.c_str () );
277 }
278 }
279
280 void
281 Project::SetConfigurationOption ( char* s,
282 string name )
283 {
284 SetConfigurationOption ( s, name, "" );
285 }
286
287 void
288 Project::WriteConfigurationFile ()
289 {
290 char* buf;
291 char* s;
292
293 buf = (char*) malloc ( 10*1024 );
294 if ( buf == NULL )
295 throw OutOfMemoryException ();
296
297 s = buf;
298 s = s + sprintf ( s, "/* Automatically generated. " );
299 s = s + sprintf ( s, "Edit config.xml to change configuration */\n" );
300 s = s + sprintf ( s, "#ifndef __INCLUDE_CONFIG_H\n" );
301 s = s + sprintf ( s, "#define __INCLUDE_CONFIG_H\n" );
302
303 SetConfigurationOption ( s, "ARCH" );
304 SetConfigurationOption ( s, "OPTIMIZED" );
305 SetConfigurationOption ( s, "MP", "UP");
306 SetConfigurationOption ( s, "ACPI" );
307 SetConfigurationOption ( s, "_3GB" );
308
309 s = s + sprintf ( s, "#endif /* __INCLUDE_CONFIG_H */\n" );
310
311 FileSupportCode::WriteIfChanged ( buf, Environment::GetIntermediatePath() + sSep + "include" + sSep + "roscfg.h" );
312
313 free ( buf );
314 }
315
316 void
317 Project::ExecuteInvocations ()
318 {
319 for ( size_t i = 0; i < modules.size (); i++ )
320 modules[i]->InvokeModule ();
321 }
322
323 void
324 Project::ReadXml ()
325 {
326 Path path;
327 head = XMLLoadFile ( xmlfile, path, xmlbuildfiles );
328 node = NULL;
329 for ( size_t i = 0; i < head->subElements.size (); i++ )
330 {
331 if ( head->subElements[i]->name == "project" )
332 {
333 node = head->subElements[i];
334 string path;
335 ProcessXML ( path );
336 return;
337 }
338 }
339
340 if (node == NULL)
341 node = head->subElements[0];
342
343 throw XMLInvalidBuildFileException (
344 node->location,
345 "Document contains no 'project' tag." );
346 }
347
348 void
349 Project::ProcessXML ( const string& path )
350 {
351 const XMLAttribute *att;
352 if ( node->name != "project" )
353 throw Exception ( "internal tool error: Project::ProcessXML() called with non-<project> node" );
354
355 att = node->GetAttribute ( "name", false );
356 if ( !att )
357 name = "Unnamed";
358 else
359 name = att->value;
360
361 att = node->GetAttribute ( "makefile", true );
362 assert(att);
363 makefile = Environment::GetAutomakeFile ( att->value );
364
365 size_t i;
366 for ( i = 0; i < node->subElements.size (); i++ )
367 {
368 ParseContext parseContext;
369 ProcessXMLSubElement ( *node->subElements[i], path, parseContext );
370 }
371
372 non_if_data.ProcessXML ();
373
374 non_if_data.ExtractModules( modules );
375
376 for ( i = 0; i < linkerFlags.size (); i++ )
377 linkerFlags[i]->ProcessXML ();
378 for ( i = 0; i < modules.size (); i++ )
379 modules[i]->ProcessXML ();
380 for ( i = 0; i < cdfiles.size (); i++ )
381 cdfiles[i]->ProcessXML ();
382 for ( i = 0; i < installfiles.size (); i++ )
383 installfiles[i]->ProcessXML ();
384 }
385
386 void
387 Project::ProcessXMLSubElement ( const XMLElement& e,
388 const string& path,
389 ParseContext& parseContext )
390 {
391 bool subs_invalid = false;
392
393 string subpath(path);
394 if ( e.name == "module" )
395 {
396 Module* module = new Module ( *this, e, path );
397 if ( LocateModule ( module->name ) )
398 throw XMLInvalidBuildFileException (
399 node->location,
400 "module name conflict: '%s' (originally defined at %s)",
401 module->name.c_str(),
402 module->node.location.c_str() );
403 non_if_data.modules.push_back ( module );
404 return; // defer processing until later
405 }
406 else if ( e.name == "cdfile" )
407 {
408 CDFile* cdfile = new CDFile ( *this, e, path );
409 cdfiles.push_back ( cdfile );
410 subs_invalid = true;
411 }
412 else if ( e.name == "installfile" )
413 {
414 InstallFile* installfile = new InstallFile ( *this, e, path );
415 installfiles.push_back ( installfile );
416 subs_invalid = true;
417 }
418 else if ( e.name == "directory" )
419 {
420 const XMLAttribute* att = e.GetAttribute ( "name", true );
421 assert(att);
422 subpath = GetSubPath ( *this, e.location, path, att->value );
423 }
424 else if ( e.name == "include" )
425 {
426 Include* include = new Include ( *this, &e );
427 non_if_data.includes.push_back ( include );
428 subs_invalid = true;
429 }
430 else if ( e.name == "define" )
431 {
432 Define* define = new Define ( *this, e );
433 non_if_data.defines.push_back ( define );
434 subs_invalid = true;
435 }
436 else if ( e.name == "compilerflag" )
437 {
438 CompilerFlag* pCompilerFlag = new CompilerFlag ( *this, e );
439 non_if_data.compilerFlags.push_back ( pCompilerFlag );
440 subs_invalid = true;
441 }
442 else if ( e.name == "linkerflag" )
443 {
444 linkerFlags.push_back ( new LinkerFlag ( *this, e ) );
445 subs_invalid = true;
446 }
447 else if ( e.name == "if" || e.name == "ifnot" )
448 {
449 const XMLAttribute* name;
450 name = e.GetAttribute ( "property", true );
451 assert( name );
452 const Property *property = LookupProperty( name->value );
453 if ( !property )
454 {
455 // Property not found
456 throw InvalidOperationException ( __FILE__,
457 __LINE__,
458 "Test on unknown property '%s' at %s",
459 name->value.c_str (), e.location.c_str () );
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 = ( property->value == 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.push_back ( 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 for ( size_t i = 0; i < modules.size (); i++ )
497 {
498 if (modules[i]->name == name)
499 return modules[i];
500 }
501
502 return NULL;
503 }
504
505 const Module*
506 Project::LocateModule ( const string& name ) const
507 {
508 for ( size_t i = 0; i < modules.size (); i++ )
509 {
510 if ( modules[i]->name == name )
511 return modules[i];
512 }
513
514 return NULL;
515 }
516
517 const std::string&
518 Project::GetProjectFilename () const
519 {
520 return xmlfile;
521 }