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