+#include <msp/fs/dir.h>
+#include <msp/fs/stat.h>
+#include <msp/fs/utils.h>
+#include <msp/io/print.h>
+#include <msp/strings/format.h>
+#include <msp/strings/utils.h>
+#include <msp/time/units.h>
+#include <msp/time/utils.h>
+#include "binarypackage.h"
+#include "builder.h"
+#include "externaltask.h"
+#include "package.h"
+#include "packagemanager.h"
+
+using namespace std;
+using namespace Msp;
+
+PackageManager::PackageManager(Builder &b):
+ builder(b),
+ no_externals(false)
+{
+ pkg_path.push_back(builder.get_cwd()/".");
+ pkg_path.push_back(builder.get_cwd()/"..");
+}
+
+PackageManager::~PackageManager()
+{
+ for(PackageMap::iterator i=packages.begin(); i!=packages.end(); ++i)
+ delete i->second;
+}
+
+void PackageManager::set_no_externals(bool x)
+{
+ no_externals = x;
+}
+
+void PackageManager::add_package(Package *pkg)
+{
+ PackageMap::iterator i = packages.find(pkg->get_name());
+ if(i!=packages.end())
+ {
+ if(i->second!=pkg)
+ throw invalid_argument("Package name conflict");
+ else
+ throw logic_error("Package is already managed");
+ }
+
+ packages.insert(PackageMap::value_type(pkg->get_name(), pkg));
+}
+
+Package *PackageManager::find_package(const string &name)
+{
+ PackageMap::iterator i = packages.find(format("%s/%s", name, builder.get_current_arch().get_system()));
+ if(i==packages.end())
+ i = packages.find(name);
+ if(i!=packages.end())
+ return i->second;
+
+ if(!no_externals)
+ {
+ FS::Path path = get_package_location(name);
+ if(!path.empty() && !builder.load_build_file(path/"Build"))
+ {
+ i = packages.find(name);
+ if(i!=packages.end())
+ return i->second;
+ }
+ }
+
+ Package *pkg = 0;
+ try
+ {
+ // Package source not found - create a binary package
+ string flags_str = run_pkgconfig(name, "flags");
+ vector<string> flags = split(flags_str);
+ pkg = BinaryPackage::from_flags(builder, name, flags);
+ }
+ catch(...)
+ {
+ builder.problem(name, "not found");
+ }
+
+ packages.insert(PackageMap::value_type(name, pkg));
+
+ return pkg;
+}
+
+string PackageManager::run_pkgconfig(const string &pkg, const string &what)
+{
+ ExternalTask::Arguments argv;
+ argv.push_back("pkg-config");
+ if(what=="cflags" || what=="libs")
+ argv.push_back("--"+what);
+ else if(what=="flags")
+ {
+ argv.push_back("--cflags");
+ argv.push_back("--libs");
+ }
+ else
+ argv.push_back("--variable="+what);
+ argv.push_back(pkg);
+
+ if(builder.get_verbose()>=4)
+ IO::print("Running %s\n", join(argv.begin(), argv.end()));
+
+ ExternalTask task(argv);
+ task.set_stdout(ExternalTask::CAPTURE);
+ task.set_stderr(ExternalTask::IGNORE);
+ task.start();
+ Task::Status status;
+ while((status=task.check())==Task::RUNNING)
+ Time::sleep(10*Time::msec);
+ if(status==Task::ERROR)
+ throw runtime_error(format("pkg-config for package %s failed", pkg));
+
+ return task.get_output();
+}
+
+FS::Path PackageManager::get_package_location(const string &name)
+{
+ if(builder.get_verbose()>=3)
+ IO::print("Looking for package %s\n", name);
+
+ try
+ {
+ // Try to get source directory with pkgconfig
+ string srcdir = strip(run_pkgconfig(name, "source"));
+ if(!srcdir.empty())
+ return srcdir;
+ }
+ catch(...)
+ { }
+
+ if(pkg_dirs.empty())
+ {
+ for(list<FS::Path>::const_iterator i=pkg_path.begin(); i!=pkg_path.end(); ++i)
+ {
+ list<string> files = list_files(*i);
+ for(list<string>::const_iterator j=files.begin(); j!=files.end(); ++j)
+ {
+ FS::Path full = *i / *j;
+ if(FS::exists(full/"Build"))
+ pkg_dirs.push_back(full);
+ }
+ }
+ if(builder.get_verbose()>=3)
+ IO::print("%d packages found in path\n", pkg_dirs.size());
+ }
+
+ bool msp = !name.compare(0, 3, "msp");
+ for(list<FS::Path>::const_iterator i=pkg_dirs.begin(); i!=pkg_dirs.end(); ++i)
+ {
+ string base = basename(*i);
+ unsigned dash = base.rfind('-');
+
+ if(!base.compare(0, dash, name))
+ return *i;
+ else if(msp && !base.compare(0, dash-3, name, 3, string::npos))
+ return *i;
+ }
+
+ return FS::Path();
+}