]> git.tdb.fi Git - builder.git/blob - source/builder.cpp
Change conf_all into a normal commandline option
[builder.git] / source / builder.cpp
1 #include <fstream>
2 #include <msp/progress.h>
3 #include <msp/strconv.h>
4 #include <msp/strutils.h>
5 #include <msp/core/error.h>
6 #include <msp/getopt++/getopt++.h>
7 #include <msp/parser/parser.h>
8 #include <msp/path/utils.h>
9 #include <msp/time/units.h>
10 #include <msp/time/utils.h>
11 #include "action.h"
12 #include "analyzer.h"
13 #include "builder.h"
14 #include "executable.h"
15 #include "header.h"
16 #include "install.h"
17 #include "misc.h"
18 #include "objectfile.h"
19 #include "package.h"
20 #include "systemlibrary.h"
21 #include "virtualtarget.h"
22
23 using namespace std;
24 using namespace Msp;
25
26 Builder::Builder(int argc, char **argv):
27         verbose(1),
28         cwd(Path::getcwd()),
29         build_file("Build"),
30         do_build(true),
31         analyzer(0),
32         jobs(1),
33         chrome(false)
34 {
35         GetOpt getopt;
36         getopt.add_option(GetOpt::Option('v', "verbose", GetOpt::NONE));
37         getopt.add_option(GetOpt::Option('a', "analyze", GetOpt::REQUIRED));
38         getopt.add_option(GetOpt::Option('b', "build", GetOpt::NONE));
39         getopt.add_option(GetOpt::Option("max-depth", GetOpt::REQUIRED));
40         getopt.add_option(GetOpt::Option('n', "dry-run", GetOpt::NONE));
41         getopt.add_option(GetOpt::Option('W', "what-if", GetOpt::REQUIRED));
42         getopt.add_option(GetOpt::Option('B', "build-all", GetOpt::NONE));
43         getopt.add_option(GetOpt::Option('C', "chdir", GetOpt::REQUIRED));
44         getopt.add_option(GetOpt::Option('j', "jobs", GetOpt::REQUIRED, "1"));
45         getopt.add_option(GetOpt::Option('h', "help", GetOpt::NONE));
46         getopt.add_option(GetOpt::Option('c', "clean", GetOpt::NONE));
47         getopt.add_option(GetOpt::Option('f', "file", GetOpt::REQUIRED, "Build"));
48         getopt.add_option(GetOpt::Option("chrome", GetOpt::NONE));
49         getopt.add_option(GetOpt::Option("full-paths", GetOpt::NONE));
50         getopt.add_option(GetOpt::Option('A', "conf-all", GetOpt::NONE));
51         int index=getopt(argc, argv);
52
53         verbose+=getopt['v'].count();
54
55         if(getopt['a'])
56         {
57                 analyzer=new Analyzer(*this);
58
59                 string mode=getopt['a'].arg();
60                 if(mode=="deps")
61                         analyzer->set_mode(Analyzer::DEPS);
62                 else if(mode=="alldeps")
63                         analyzer->set_mode(Analyzer::ALLDEPS);
64                 else if(mode=="rebuild")
65                         analyzer->set_mode(Analyzer::REBUILD);
66                 else if(mode=="rdeps")
67                         analyzer->set_mode(Analyzer::RDEPS);
68                 else
69                         throw UsageError("Invalid analysis mode");
70
71                 if(getopt["max-depth"])
72                         analyzer->set_max_depth(strtol(getopt["max-depth"].arg()));
73                 analyzer->set_full_paths(getopt["full-paths"]);
74
75                 if(!getopt['b'])
76                         do_build=false;
77         }
78
79         dry_run=getopt['n'];
80
81         jobs=max(strtol(getopt['j'].arg()), 1L);
82
83         chrome=getopt["chrome"];
84
85         conf_all=getopt['A'];
86
87         if(getopt['C'])
88                 chdir(getopt['C'].arg().c_str());
89
90         build_file=getopt['f'].arg();
91
92         for(int i=index; i<argc; ++i)
93         {
94                 string v(argv[i]);
95                 unsigned equal=v.find('=');
96                 if(equal!=string::npos)
97                         cmdline_options.insert(RawOptionMap::value_type(v.substr(0, equal), v.substr(equal+1)));
98                 else
99                         cmdline_targets.push_back(argv[i]);
100         }
101
102         if(cmdline_targets.empty())
103                 cmdline_targets.push_back("default");
104
105         if(getopt['W'])
106                 what_if.push_back(getopt['W'].arg());
107 }
108
109 /**
110 Gets a package with the specified name, possibly creating it.
111
112 @param   n  Package name
113
114 @return  Pointer to the package, or 0 if the package could not be located
115 */
116 Package *Builder::get_package(const string &n)
117 {
118         PackageMap::iterator i=packages.find(n);
119         if(i!=packages.end())
120                 return i->second;
121
122         // Try to get source directory with pkgconfig
123         list<string> argv;
124         argv.push_back("pkg-config");
125         argv.push_back("--variable=source");
126         argv.push_back(n);
127         string srcdir=strip(run_command(argv));
128         
129         PathList dirs;
130         if(!srcdir.empty())
131                 dirs.push_back(srcdir);
132
133         // Make some other guesses about the source directory
134         string dirname=n;
135         if(!dirname.compare(0, 3, "msp"))
136                 dirname.erase(0, 3);
137         dirs.push_back(cwd/dirname);
138         dirs.push_back(cwd/".."/dirname);
139
140         // Go through the candidate directories and look for a Build file
141         for(PathList::iterator j=dirs.begin(); j!=dirs.end(); ++j)
142                 if(!load_build_file(*j/"Build"))
143                 {
144                         i=packages.find(n);
145                         if(i!=packages.end())
146                                 return i->second;
147                         break;
148                 }
149
150         // Package source not found - create a binary package
151         Package *pkg=Package::create(*this, n);
152         packages.insert(PackageMap::value_type(n, pkg));
153         if(pkg)
154                 new_pkgs.push_back(pkg);
155
156         return pkg;
157 }
158
159 /**
160 Returns the target with the given name, or 0 if no such target exists.
161 */
162 Target *Builder::get_target(const string &n)
163 {
164         TargetMap::iterator i=targets.find(n);
165         if(i!=targets.end())
166                 return i->second;
167         return 0;
168 }
169
170 /**
171 Tries to locate a header included from a given location and with a given include
172 path.  Considers known targets as well as existing files.
173 */
174 Target *Builder::get_header(const string &include, const string &from, const list<string> &path)
175 {
176         string hash(8, 0);
177         update_hash(hash, from);
178         for(list<string>::const_iterator i=path.begin(); i!=path.end(); ++i)
179                 update_hash(hash, *i);
180
181         string id=hash+include;
182         TargetMap::iterator i=includes.find(id);
183         if(i!=includes.end())
184                 return i->second;
185
186         string fn=include.substr(1);
187         Target *tgt=0;
188         if(include[0]=='"' && (tgt=check_header(Path::Path(from)/fn)))
189                 return tgt;
190         if((tgt=check_header(Path::Path("/usr/include")/fn)))
191                 return tgt;
192         //XXX Determine the C++ header location dynamically
193         if((tgt=check_header(Path::Path("/usr/include/c++/4.1.2")/fn)))
194                 return tgt;
195         for(list<string>::const_iterator j=path.begin(); j!=path.end(); ++j)
196                 if((tgt=check_header(Path::Path(*j)/fn)))
197                         return tgt;
198         
199         return 0;
200 }
201
202 Target *Builder::get_library(const string &lib, const list<string> &path)
203 {
204         string hash(8, 0);
205         for(list<string>::const_iterator i=path.begin(); i!=path.end(); ++i)
206                 update_hash(hash, *i);
207
208         string id=hash+lib;
209         TargetMap::iterator i=libraries.find(id);
210         if(i!=libraries.end())
211                 return i->second;
212
213         string basename="lib"+lib+".so";
214         for(list<string>::const_iterator j=path.begin(); j!=path.end(); ++j)
215         {
216                 string full=(Path::Path(*j)/basename).str();
217                 Target *tgt=get_target(full);
218                 if(tgt) return tgt;
219                 
220                 if(Path::exists(full))
221                 {
222                         add_target(tgt=new SystemLibrary(*this, full));
223                         return tgt;
224                 }
225         }
226
227         return 0;
228 }
229
230 int Builder::main()
231 {
232         if(load_build_file(cwd/build_file))
233         {
234                 cerr<<"No build info here.\n";
235                 return 1;
236         }
237
238         default_pkg=packages.begin()->second;
239
240         while(!new_pkgs.empty())
241         {
242                 Package *pkg=new_pkgs.front();
243                 if(pkg==default_pkg || conf_all)
244                         pkg->process_options(cmdline_options);
245                 new_pkgs.erase(new_pkgs.begin());
246                 pkg->resolve_refs();
247         }
248
249         std::list<std::string> missing;
250         for(PackageMap::iterator i=packages.begin(); i!=packages.end(); ++i)
251                 if(!i->second)
252                         missing.push_back(i->first);
253
254         if(!missing.empty())
255         {
256                 missing.sort();
257                 cerr<<"The following packages were not found on the system:\n";
258                 for(list<string>::iterator i=missing.begin(); i!=missing.end(); ++i)
259                         cerr<<"  "<<*i<<'\n';
260                 cerr<<"Please install them and try again.\n";
261                 return 1;
262         }
263
264         default_pkg->create_build_info();
265
266         if(create_targets())
267                 return 1;
268
269         cout<<packages.size()<<" packages, "<<targets.size()<<" targets\n";
270         if(verbose>=2)
271         {
272                 for(PackageMap::iterator i=packages.begin(); i!=packages.end(); ++i)
273                 {
274                         cout<<' '<<i->second->get_name();
275                         if(i->second->get_buildable())
276                                 cout<<'*';
277                         unsigned count=0;
278                         for(TargetMap::iterator j=targets.begin(); j!=targets.end(); ++j)
279                                 if(j->second->get_package()==i->second)
280                                         ++count;
281                         cout<<" ("<<count<<" targets)\n";
282                 }
283         }
284
285         if(analyzer)
286                 analyzer->analyze();
287
288         if(do_build)
289                 build();
290
291         return exit_code;
292 }
293
294 Builder::~Builder()
295 {
296         for(PackageMap::iterator i=packages.begin(); i!=packages.end(); ++i)
297                 delete i->second;
298         for(TargetMap::iterator i=targets.begin(); i!=targets.end(); ++i)
299                 delete i->second;
300         delete analyzer;
301 }
302
303 int Builder::load_build_file(const Path::Path &fn)
304 {
305         ifstream in(fn.str().c_str());
306         if(!in)
307                 return -1;
308
309         Parser::Parser parser(in, fn.str());
310         Loader loader(*this, fn.subpath(0, fn.size()-1));
311         loader.load(parser);
312
313         return 0;
314 }
315
316 int Builder::create_targets()
317 {
318         Target *world=new VirtualTarget(*this, "world");
319         add_target(world);
320
321         Target *def_tgt=new VirtualTarget(*this, "default");
322         add_target(def_tgt);
323         world->add_depend(def_tgt);
324
325         Target *install=new VirtualTarget(*this, "install");
326         add_target(install);
327         world->add_depend(install);
328
329         for(PackageMap::iterator i=packages.begin(); i!=packages.end(); ++i)
330         {
331                 if(!i->second)
332                         continue;
333                 if(!i->second->get_buildable())
334                         continue;
335
336                 Path::Path inst_base;
337                 if(i->second->get_config().is_option("prefix"))
338                         inst_base=i->second->get_config().get_option("prefix").value;
339
340                 const ComponentList &components=i->second->get_components();
341                 for(ComponentList::const_iterator j=components.begin(); j!=components.end(); ++j)
342                 {
343                         PathList files;
344                         const PathList &sources=j->get_sources();
345                         for(PathList::const_iterator k=sources.begin(); k!=sources.end(); ++k)
346                         {
347                                 struct stat st;
348                                 stat(*k, st);
349                                 if(S_ISDIR(st.st_mode))
350                                 {
351                                         list<string> sfiles=list_files(*k);
352                                         for(list<string>::iterator l=sfiles.begin(); l!=sfiles.end(); ++l)
353                                                 files.push_back(*k / *l);
354                                 }
355                                 else
356                                         files.push_back(*k);
357                         }
358
359                         bool build_exe=j->get_type()!=Component::HEADERS;
360                         
361                         list<ObjectFile *> objs;
362                         for(PathList::iterator k=files.begin(); k!=files.end(); ++k)
363                         {
364                                 string basename=(*k)[-1];
365                                 string ext=Path::splitext(basename).ext;
366                                 if(ext==".cpp" || ext==".c")
367                                 {
368                                         if(build_exe)
369                                         {
370                                                 SourceFile *src=new SourceFile(*this, &*j, k->str());
371                                                 add_target(src);
372                                                 
373                                                 ObjectFile *obj=new ObjectFile(*this, *j, *src);
374                                                 add_target(obj);
375                                                 objs.push_back(obj);
376                                         }
377                                 }
378                                 else if(ext==".h")
379                                 {
380                                         Target *hdr=get_target(k->str());
381                                         if(!hdr)
382                                         {
383                                                 hdr=new Header(*this, &*j, k->str());
384                                                 add_target(hdr);
385                                         }
386                                         if(!j->get_install_headers().empty())
387                                         {
388                                                 Path::Path inst_path=inst_base/"include"/j->get_install_headers()/basename;
389                                                 Install *inst=new Install(*this, *i->second, *hdr, inst_path.str());
390                                                 add_target(inst);
391                                                 install->add_depend(inst);
392                                         }
393                                 }
394                         }
395
396                         if(build_exe)
397                         {
398                                 Executable *exe=new Executable(*this, *j, objs);
399                                 add_target(exe);
400                                 if(i->second==default_pkg)
401                                         def_tgt->add_depend(exe);
402                                 else
403                                         world->add_depend(exe);
404
405                                 if(j->get_install())
406                                 {
407                                         string inst_dir;
408                                         if(j->get_type()==Component::PROGRAM)
409                                                 inst_dir="bin";
410                                         else if(j->get_type()==Component::LIBRARY)
411                                                 inst_dir="lib";
412                                         if(!inst_dir.empty())
413                                         {
414                                                 Install *inst=new Install(*this, *i->second, *exe, (inst_base/inst_dir/Path::basename(exe->get_name())).str());
415                                                 add_target(inst);
416                                                 install->add_depend(inst);
417                                         }
418                                 }
419                         }
420                 }
421         }
422
423         while(!new_tgts.empty())
424         {
425                 Target *tgt=new_tgts.front();
426                 new_tgts.erase(new_tgts.begin());
427                 tgt->find_depends();
428                 if(!tgt->get_depends_ready())
429                         new_tgts.push_back(tgt);
430         }
431
432         for(list<string>::iterator i=what_if.begin(); i!=what_if.end(); ++i)
433         {
434                 Target *tgt=get_target((cwd/ *i).str());
435                 if(!tgt)
436                 {
437                         cerr<<"Unknown what-if target "<<*i<<'\n';
438                         return -1;
439                 }
440                 tgt->touch();
441         }
442
443         Target *cmdline=new VirtualTarget(*this, "cmdline");
444         add_target(cmdline);
445         world->add_depend(cmdline);
446         for(list<string>::iterator i=cmdline_targets.begin(); i!=cmdline_targets.end(); ++i)
447         {
448                 Target *tgt=get_target(*i);
449                 if(!tgt)
450                         tgt=get_target((cwd/ *i).str());
451                 if(!tgt)
452                 {
453                         cerr<<"I don't know anything about "<<*i<<'\n';
454                         return -1;
455                 }
456                 cmdline->add_depend(tgt);
457         }
458
459         world->prepare();
460
461         return 0;
462 }
463
464 Target *Builder::check_header(const Msp::Path::Path &fn)
465 {
466         Target *tgt=get_target(fn.str());
467         if(tgt) return tgt;
468
469         if(Path::exists(fn))
470         {
471                 add_target(tgt=new SystemHeader(*this, fn.str()));
472                 return tgt;
473         }
474         return 0;
475 }
476
477 void Builder::add_target(Target *t)
478 {
479         targets.insert(TargetMap::value_type(t->get_name(), t));
480         new_tgts.push_back(t);
481 }
482
483 void Builder::update_hash(string &hash, const string &value)
484 {
485         for(unsigned i=0; i<value.size(); ++i)
486                 hash[i%hash.size()]^=value[i];
487 }
488
489 /**
490 This function supervises the build process, starting new actions when slots
491 become available.
492 */
493 int Builder::build()
494 {
495         Target *cmdline=get_target("cmdline");
496
497         unsigned total=cmdline->count_rebuild();
498         if(!total)
499         {
500                 cout<<"Already up to date\n";
501                 return 0;
502         }
503         cout<<"Will build "<<total<<" target(s)\n";
504
505         vector<Action *> actions;
506
507         if(chrome)
508                 cout<<"0 targets built\n";
509         unsigned count=0;
510
511         bool fail=false;
512         bool finish=false;
513
514         while(!finish)
515         {
516                 if(actions.size()<jobs && !fail)
517                 {
518                         Target *tgt=cmdline->get_buildable_target();
519                         if(tgt)
520                         {
521                                 Action *action=tgt->build();
522                                 if(action)
523                                         actions.push_back(action);
524                         }
525                         else if(actions.empty())
526                                 finish=true;
527                 }
528                 else
529                         Time::sleep(10*Time::msec);
530
531                 for(unsigned i=0; i<actions.size();)
532                 {
533                         int status=actions[i]->check();
534                         if(status>=0)
535                         {
536                                 ++count;
537                                 if(chrome)
538                                 {
539                                         cout<<"\e["<<actions.size()+1<<'A';
540                                         cout<<count<<" targets built\n";
541                                         if(i)
542                                                 cout<<"\e["<<i<<"B";
543                                         cout<<"\e[M";
544                                         if(i<actions.size()-1)
545                                                 cout<<"\e["<<actions.size()-i-1<<"B";
546                                         cout.flush();
547                                 }
548                                 delete actions[i];
549                                 actions.erase(actions.begin()+i);
550                                 if(status>0)
551                                         fail=true;
552                                 if(actions.empty() && fail)
553                                         finish=true;
554                         }
555                         else
556                                 ++i;
557                 }
558         }
559
560         return fail?-1:0;
561 }
562
563 Application::RegApp<Builder> Builder::reg;
564
565 Builder::Loader::Loader(Builder &b, const Path::Path &s):
566         bld(b),
567         src(s)
568 {
569         add("package", &Loader::package);
570 }
571
572 void Builder::Loader::package(const string &n)
573 {
574         Package *pkg=new Package(bld, n, src);
575         load_sub(*pkg);
576         bld.packages.insert(PackageMap::value_type(n, pkg));
577         bld.new_pkgs.push_back(pkg);
578 }
579