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