using namespace std;
+/**
+Annouces the action by printing out the package name, tool and target name.
+*/
void Action::announce(const string &pkg, const string &tool, const string &tgt)
{
ostringstream line;
class Builder;
+/**
+Actions are executed to rebuild targets.
+*/
class Action
{
public:
+ /// Emitted when the action has finished
sigc::signal<void> signal_done;
+ /**
+ Checks whether the action is done and emits signal_done if it is.
+
+ @return 0 on successful completion, 1 on error, -1 if the action is still
+ executing
+ */
virtual int check()=0;
+
virtual ~Action() { }
protected:
Builder &builder;
Analyzer::Analyzer(Builder &b):
builder(b),
+ mode(DEPS),
max_depth(0),
full_paths(false)
{ }
+/**
+Performs the analysis and prints out the resulting dependency tree.
+*/
void Analyzer::analyze()
{
TableRow row;
print_table();
}
+/**
+Adds rows to the table for the given target and it' dependencies.
+
+@param tgt Target to be processed
+@param depth Recursion level of the target (top level is 0)
+*/
void Analyzer::build_depend_table(Target &tgt, unsigned depth)
{
if(mode!=REBUILD && mode!=ALLDEPS)
{
+ // Skip trivial targets
if(dynamic_cast<ObjectFile *>(&tgt))
return build_depend_table(*tgt.get_depends().front(), depth);
else if(dynamic_cast<Install *>(&tgt))
return build_depend_table(*tgt.get_depends().front(), depth);
}
else if(mode==REBUILD && !tgt.get_rebuild())
+ /* All targets that depend on to-be-built targets will be rebuilt
+ themselves, so we cn stop here. */
return;
TableRow row;
if(!max_depth || depth<max_depth-1)
{
const TargetList &depends=tgt.get_depends();
+ //XXX If we want to sort the targets, we need to take the value of full_paths into account
//depends.sort(target_order);
for(TargetList::const_iterator i=depends.begin(); i!=depends.end(); ++i)
build_depend_table(**i, depth+1);
}
}
+/**
+Prints out the table that resulted from the analysis.
+*/
void Analyzer::print_table() const
{
vector<unsigned> col_width;
+ // Determine column widths
for(Table::const_iterator i=table.begin(); i!=table.end(); ++i)
{
if(col_width.size()<i->size())
class Builder;
class Target;
+/**
+Performs various kinds of dependency analysis on the build tree.
+*/
class Analyzer
{
public:
enum Mode
{
- DEPS,
- ALLDEPS,
- REBUILD,
- RDEPS
+ DEPS, /// Skip over "trivial" targets such as Install and Compile
+ ALLDEPS, /// Print out absolutely every target
+ REBUILD, /// Print targets that are going to be rebuilt
+ RDEPS /// Print targets that depend on the given targets (NYI)
};
Analyzer(Builder &);
class StaticLibrary;
+/**
+Creates an archive of object files, a.k.a. static library.
+*/
class Archive: public ExternalAction
{
public:
/**
Tries to locate a header included from a given location and with a given include
-path. Considers known targets as well as existing files.
+path. Considers known targets as well as existing files. If a matching target
+is not found but a file exists, a new SystemHeader target will be created and
+returned.
*/
Target *Builder::get_header(const string &include, const string &from, const list<string> &path)
{
return 0;
}
+/**
+Tries to locate a library with the given library path. Considers known targets
+as well as existing files. If a matching target is not found but a file exists,
+a new SystemLibrary target will be created and returned.
+*/
Target *Builder::get_library(const string &lib, const list<string> &path)
{
string hash(8, 0);
}
}
+/**
+Loads the given build file.
+
+@param fn Path to the file
+
+@return 0 on success, -1 if the file could not be opened
+*/
int Builder::load_build_file(const Path::Path &fn)
{
ifstream in(fn.str().c_str());
return 0;
}
+/**
+Creates targets for all packages and prepares them for building.
+
+@return 0 if everything went ok, -1 if something bad happened and a build
+ shouldn't be attempted
+*/
int Builder::create_targets()
{
Target *world=new VirtualTarget(*this, "world");
const ComponentList &components=i->second->get_components();
for(ComponentList::const_iterator j=components.begin(); j!=components.end(); ++j)
{
+ // Collect all files belonging to the component
PathList files;
const PathList &sources=j->get_sources();
for(PathList::const_iterator k=sources.begin(); k!=sources.end(); ++k)
{
string basename=(*k)[-1];
string ext=Path::splitext(basename).ext;
- if(ext==".cpp" || ext==".c")
+ if((ext==".cpp" || ext==".c") && build_exe)
{
- if(build_exe)
- {
- SourceFile *src=new SourceFile(*this, &*j, k->str());
- add_target(src);
-
- ObjectFile *obj=new ObjectFile(*this, *j, *src);
- add_target(obj);
- objs.push_back(obj);
- }
+ SourceFile *src=new SourceFile(*this, &*j, k->str());
+ add_target(src);
+
+ // Compile sources
+ ObjectFile *obj=new ObjectFile(*this, *j, *src);
+ add_target(obj);
+ objs.push_back(obj);
}
else if(ext==".h")
{
hdr=new Header(*this, &*j, k->str());
add_target(hdr);
}
+
+ // Install headers if requested
if(!j->get_install_headers().empty())
{
Path::Path inst_path=inst_base/"include"/j->get_install_headers()/basename;
}
}
+ // Find dependencies until no new targets are created
while(!new_tgts.empty())
{
Target *tgt=new_tgts.front();
new_tgts.push_back(tgt);
}
- for(list<string>::iterator i=what_if.begin(); i!=what_if.end(); ++i)
+ // Apply what-ifs
+ for(StringList::iterator i=what_if.begin(); i!=what_if.end(); ++i)
{
Target *tgt=get_target((cwd/ *i).str());
if(!tgt)
tgt->touch();
}
+ // Make the cmdline target depend on all targets mentioned on the command line
Target *cmdline=new VirtualTarget(*this, "cmdline");
add_target(cmdline);
world->add_depend(cmdline);
return 0;
}
+/**
+Check if a header exists, either as a target or a file. Either an existing
+target or a new SystemHeader target will be returned.
+*/
Target *Builder::check_header(const Msp::Path::Path &fn)
{
Target *tgt=get_target(fn.str());
return 0;
}
+/**
+Adds a target to both the target map and the new target queue.
+*/
void Builder::add_target(Target *t)
{
targets.insert(TargetMap::value_type(t->get_name(), t));
new_tgts.push_back(t);
}
+/**
+Updates a hash with a string. This is used from get_header and get_library.
+*/
void Builder::update_hash(string &hash, const string &value)
{
for(unsigned i=0; i<value.size(); ++i)
return fail?-1:0;
}
+/**
+Prints out information about the default package.
+*/
void Builder::package_help()
{
const Config &config=default_pkg->get_config();
class Analyzer;
class Package;
+/**
+The main application class. Controls and owns everything. Rules the world.
+*/
class Builder: public Msp::Application
{
public:
Builder(int, char **);
- unsigned get_verbose() const { return verbose; }
- bool get_dry_run() const { return dry_run; }
+ unsigned get_verbose() const { return verbose; }
+ bool get_dry_run() const { return dry_run; }
bool get_build_all() const { return build_all; }
Package *get_package(const std::string &);
Target *get_target(const std::string &);
using namespace Msp;
+/**
+Adds another BuildInfo to the end of this one.
+*/
void BuildInfo::add(const BuildInfo &bi)
{
cflags.insert(cflags.end(), bi.cflags.begin(), bi.cflags.end());
libs.insert(libs.end(), bi.libs.begin(), bi.libs.end());
}
+/**
+Makes sure there are no duplicate entries in the lists.
+*/
void BuildInfo::unique()
{
unique(cflags);
unique(libs);
}
+/**
+Removes any duplicate entries from a list, leaving only the first one. The
+order of other elements is preserved. O(n²) efficiency.
+*/
void BuildInfo::unique(StringList &l)
{
StringList l2;
#include <msp/parser/loader.h>
#include "misc.h"
+/**
+Stores information about compiler command line parameters in a more abstract
+form. Allows combining with other BuildInfos to support package dependencies.
+*/
class BuildInfo
{
public:
class Component;
class ObjectFile;
+/**
+Compiles a source file into an object file.
+*/
class Compile: public ExternalAction
{
public:
install(false)
{ }
+/**
+Tries to resolve all references to packages.
+*/
void Component::resolve_refs()
{
for(PkgRefList::iterator i=requires.begin(); i!=requires.end(); ++i)
i->resolve();
}
+/**
+Prepares the build information for building.
+*/
void Component::create_build_info()
{
build_info.add(pkg.get_build_info());
class Package;
+/**
+Components specify things to be built. Each component may build one binary (it
+may also build none), as well as install a bunch of headers. Components inherit
+dependencies and build info from the package they belong to, and may also add
+their own.
+*/
class Component
{
public:
+ /// Loads a Component from file. Used from Package::Loader.
class Loader: public Msp::Parser::Loader
{
public:
using namespace std;
using namespace Msp;
+/**
+Adds a configuration option.
+
+@param n Option name
+@param v Default value
+@param d Description
+*/
void Config::add_option(const string &n, const string &v, const string &d)
{
options.insert(OptionMap::value_type(n, Option(n, v, d)));
}
+/**
+Gets the given option from the configuration. If the option doesn't exist,
+an Exception is thrown.
+*/
const Config::Option &Config::get_option(const string &name) const
{
OptionMap::const_iterator i=options.find(name);
return i->second;
}
+/**
+Checks whether an option with the given name exists.
+*/
bool Config::is_option(const string &name) const
{
return options.count(name);
}
+/**
+Processes options from the given raw option map. Nonexistent options are
+ignored. If any options were changed, the mtime of the configuration is updated
+to the current time.
+
+@param opts A map to process options from
+
+@return Whether any option values were changed
+*/
bool Config::process(const RawOptionMap &opts)
{
bool changed=false;
return changed;
}
+/**
+Loads configuration from a file, if it exists.
+*/
void Config::load(const Path::Path &fn)
{
ifstream in(fn.str().c_str());
#include <msp/parser/loader.h>
#include <msp/path/path.h>
#include <msp/time/timestamp.h>
-#include "option.h"
typedef std::map<std::string, std::string> RawOptionMap;
+/**
+Manages configuration for a package. A configuration may have an arbitary
+amount of options, as well as a modification time (mtime).
+*/
class Config
{
public:
+ /**
+ A single configuration option.
+ */
struct Option
{
std::string name;
int Copy::check()
{
- if(!worker)
+ if(!worker) // True for dry run
{
signal_done.emit();
return 0;
{
Path::mkpath(copy.dest.subpath(0, copy.dest.size()-1), 0755);
+ // Remove old file. Not doing this would cause Bad Stuff when installing libraries.
if(unlink(copy.dest.str().c_str())<0 && errno!=ENOENT)
{
int err=errno;
return;
}
+ // Actual transfer loop
char buf[16384];
while(!in.eof())
{
out.write(buf, in.gcount());
}
+ // Preserve file permissions
struct stat st;
Path::stat(copy.src, st);
chmod(copy.dest.str().c_str(), st.st_mode&0777);
class Package;
+/**
+Copies a file to another place. Used by the Install target.
+*/
class Copy: public Action
{
public:
int check();
~Copy();
private:
+ /**
+ A worker thread that actually does the data transfer.
+ */
class Worker: public Msp::Thread
{
public:
add_depend(*i);
}
+/**
+Finds and adds any required libraries to the dependencies.
+*/
void Executable::find_depends()
{
const list<string> &libs=comp.get_build_info().libs;
return Target::build(new Link(builder, *this));;
}
+/**
+Returns the name for the executable. We can't do this in the constructor since
+we need to pass the value to the Target c'tor.
+*/
string Executable::generate_target_name(const Component &c)
{
string prefix,suffix;
class Component;
class ObjectFile;
+/**
+Produces a binary file, which may be either a standalone executable or a shared
+library.
+*/
class Executable: public Target
{
public:
}
if(!pid)
- return 255;
+ return exit_code;
int status;
if(waitpid(pid, &status, WNOHANG)==pid)
exit_code=WEXITSTATUS(status);
else
exit_code=254;
+ pid=0;
return exit_code;
}
else
return -1;
}
+/**
+Starts the external program. Fill in argv before calling this.
+*/
void ExternalAction::launch()
{
if(builder.get_verbose()>=2)
#include "action.h"
#include "misc.h"
+/**
+Base class for Actions that want to execute an external program.
+*/
class ExternalAction: public Action
{
public:
#include "sourcefile.h"
+/**
+Represents a header file. Mainly exists to give extra information to the user.
+*/
class Header: public SourceFile
{
public:
const char *get_type() const { return "Header"; }
};
+/**
+A header file that doesn't belong to any known package.
+*/
class SystemHeader: public Header
{
public:
#include "target.h"
+/**
+Represents the installation of a file.
+*/
class Install: public Target
{
public:
class Executable;
+/**
+Links object files and libraries to produce an executable.
+*/
class Link: public ExternalAction
{
public:
using namespace std;
using namespace Msp;
-string run_command(const list<string> &argv)
+/**
+Runs a command and returns its output as a string. The exit status of the
+command is lost.
+*/
+string run_command(const StringList &argv)
{
int pfd[2];
pipe(pfd);
if(pid==0)
{
char *argv_[argv.size()+1];
- for(CountingIterator<const string, list<string>::const_iterator> i=argv.begin(); i!=argv.end(); ++i)
+ for(CountingIterator<const string, StringList::const_iterator> i=argv.begin(); i!=argv.end(); ++i)
argv_[i.count()]=strdup(i->c_str());
argv_[argv.size()]=0;
close(pfd[0]);
add_depend(&src);
}
+/**
+Processes as many new dependences as possible. Some may be left unprocessed
+if their own dependencies are not ready, requiring another call to this
+function. Use the get_deps_ready() function to determine whether this is the
+case.
+*/
void ObjectFile::find_depends()
{
for(TargetList::iterator i=new_deps.begin(); i!=new_deps.end();)
return Target::build(new Compile(builder, *this));
}
+/**
+Recursively looks for header targets and adds them as dependencies.
+*/
void ObjectFile::find_depends(Target *tgt)
{
const string &tname=tgt->get_name();
}
}
+/**
+Adds a target to the dependency list as well as the new dependencies list.
+*/
void ObjectFile::add_depend(Target *tgt)
{
Target::add_depend(tgt);
class Component;
class SourceFile;
+/**
+Object files are compiled from source files.
+*/
class ObjectFile: public Target
{
public:
ObjectFile(Builder &, const Component &, SourceFile &);
- const char *get_type() const { return "ObjectFile"; }
+ const char *get_type() const { return "ObjectFile"; }
const Component &get_component() const { return comp; }
void find_depends();
Action *build();
+++ /dev/null
-#include "option.h"
-
-using namespace std;
-
-Option::Option(const string &n, const string &v, const string &d):
- name(n),
- defv(v),
- descr(d),
- value(v)
-{ }
+++ /dev/null
-#ifndef OPTION_H_
-#define OPTION_H_
-
-#include <string>
-
-class Option
-{
-public:
- Option(const std::string &, const std::string &, const std::string &);
- void set_value(const std::string &v) { value=v; }
- const std::string &get_value() const { return value; }
- const std::string &get_name() const { return name; }
- const std::string &get_default_value() const { return defv; }
- const std::string &get_description() const { return descr; }
-private:
- std::string name;
- std::string defv;
- std::string descr;
- std::string value;
-};
-
-#endif
#include <iostream>
+/**
+Creates a buildable package.
+*/
Package::Package(Builder &b, const string &n, const Path::Path &s):
builder(b),
name(n),
build_info_ready(false)
{ }
+/**
+Sets the path where the package files were installed. This is only useful for
+non-buildable packages that don't use pkg-config.
+*/
void Package::set_path(const Msp::Path::Path &p)
{
path=builder.get_cwd()/p;
}
+/**
+Tries to resolve all references to dependency packages.
+*/
void Package::resolve_refs()
{
for(PkgRefList::iterator i=requires.begin(); i!=requires.end(); ++i)
i->resolve_refs();
}
+/**
+Fills in build info based on configuration.
+*/
void Package::create_build_info()
{
if(build_info_ready)
build_info_ready=true;
}
+/**
+Processes configuration options that were most likely obtained from the command
+line.
+*/
void Package::process_options(const RawOptionMap &opts)
{
if(config.process(opts))
config.save(source/".options.cache");
}
+/**
+Creates a non-buildable package with the given name. Pkg-config is tried first
+to get build information. If it fails, a built-in list of known packages is
+consulted.
+*/
Package *Package::create(Builder &b, const string &name)
{
list<string> argv;
bool need_path=false;
if(info.empty())
{
+ //XXX Put these in an external file
if(name=="opengl")
info.push_back("-lGL");
else if(name=="pthread")
}
}
+/**
+Initializes a buildable package. Mostly adds configuration options.
+*/
void Package::init_buildable()
{
buildable=true;
config.load(source/".options.cache");
}
+/**
+Checks which kinds of things the components of this package install.
+
+@return A bitmask of installed things
+*/
unsigned Package::get_install_flags()
{
unsigned flags=0;
class Builder;
+/**
+A package is a distributable piece of software. They consist of one or more
+Components and may depend on other packages. Packages also have configuration
+to determine where files are installed and which features to include (features
+NYI).
+*/
class Package
{
public:
+ /// Loads a package from a file.
class Loader: public Msp::Parser::Loader
{
public:
package(0)
{ }
+/**
+Tries to get the package from Builder if we don't have it already.
+
+@return The package pointer (0 if the package was not found)
+*/
Package *PackageRef::resolve()
{
if(!package)
class Builder;
class Package;
+/**
+A proxy class that stores a package name and possibly a pointer to the package.
+*/
class PackageRef
{
public:
#include "executable.h"
+/**
+Represents a shared library. Mainly exists to give extra information to the
+user.
+*/
class SharedLibrary: public Executable
{
public:
comp(c)
{ }
+/**
+Parses include directives from the file and looks up the appropriate targets
+from Builder.
+*/
void SourceFile::find_depends()
{
ifstream in(name.c_str());
class Component;
+/**
+Represents a C or C++ source file.
+*/
class SourceFile: public Target
{
public:
SourceFile(Builder &, const Component *, const std::string &);
const StringList &get_includes() const { return includes; }
- const char *get_type() const { return "SourceFile"; }
+ const char *get_type() const { return "SourceFile"; }
void find_depends();
Action *build() { return 0; }
private:
class Component;
class ObjectFile;
+/**
+A static library target.
+*/
class StaticLibrary: public Target
{
public:
#include "target.h"
+/**
+A library that doesn't belong to any known package.
+*/
class SystemLibrary: public Target
{
public:
using namespace std;
using namespace Msp;
+/**
+Tries to locate a target that will help getting this target built. If all
+dependencies are up-to-date, returns this target. If there are no targets
+ready to be built (maybe because they are being built right now), returns 0.
+*/
Target *Target::get_buildable_target()
{
bool self_ok=true;
dep->rdepends.push_back(this);
}
+/**
+Prepares the target by recursively preparing dependencies, then checking
+whether rebuilding is needed. A flag is used to prevent unnecessary
+executions.
+*/
void Target::prepare()
{
if(prepared)
return;
+ prepared=true;
for(TargetList::iterator i=depends.begin(); i!=depends.end(); ++i)
(*i)->prepare();
check_rebuild();
+
}
+/**
+Returns the number of targets that need to be rebuilt in order to get this
+target up-to-date.
+*/
unsigned Target::count_rebuild()
{
if(counted)
return count;
}
+/**
+Changes the mtime of the target to the current time.
+*/
void Target::touch()
{
mtime=Time::now();
rebuild_reason=reason;
}
+/**
+Checks if this target needs to be rebuilt and why.
+*/
void Target::check_rebuild()
{
if(!buildable)
mark_rebuild("Package options changed");
}
+/**
+Hooks the target up with the given action, then returns it. This should be
+called from the public build() function of buildable targets.
+*/
Action *Target::build(Action *action)
{
building=true;
return action;
}
+/**
+Handles for the build_done signal of Action.
+*/
void Target::build_done()
{
building=false;
class Target;
typedef std::list<Target *> TargetList;
+/**
+Targets make up the build graph. This class is a base for all target types and
+handles many common tasks. Most targets are associated with a file.
+*/
class Target
{
public:
void add_depend(Target *);
virtual void find_depends() { deps_ready=true; }
virtual void prepare();
+
+ /**
+ Creates and returns an Action suitable for building this target.
+ */
virtual Action *build()=0;
+
void reset_count() { counted=false; }
virtual unsigned count_rebuild();
void touch();
using namespace std;
+/**
+Virtual targets are only rebuilt if their dependencies need rebuilding.
+*/
void VirtualTarget::check_rebuild()
{
for(TargetList::iterator i=depends.begin(); (i!=depends.end() && !rebuild); ++i)
mark_rebuild(Msp::Path::basename((*i)->get_name())+" needs rebuilding");
}
+/**
+Don't count virtual targets since "building" them causes no action.
+*/
unsigned VirtualTarget::count_rebuild()
{
return Target::count_rebuild()-rebuild;
#include "target.h"
+/**
+A target that is not associated with any file.
+*/
class VirtualTarget: public Target
{
public:
- VirtualTarget(Builder &b, const std::string &n): Target(b,0,n) { }
+ VirtualTarget(Builder &b, const std::string &n): Target(b, 0, n) { }
const char *get_type() const { return "VirtualTarget"; }
Action *build() { rebuild=false; return 0; }
unsigned count_rebuild();