From d304762e4338b4a055e2be018205a00c2ca9ee2f Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 9 Mar 2018 15:06:27 +0200 Subject: Support for resolving and adding configurations --- bdep/buildfile | 3 +- bdep/config.cxx | 80 +++++++++++++++++++++++++++++++++++ bdep/config.hxx | 9 ++++ bdep/database-views.hxx | 28 +++++++++++++ bdep/database.cxx | 49 ++++++++++++++++++++-- bdep/database.hxx | 3 -- bdep/init.cxx | 100 ++++++++++++++++++++++++++++++++++++++++---- bdep/odb.sh | 8 ++++ bdep/project.cli | 12 ++++-- bdep/project.cxx | 108 ++++++++++++++++++++++++++++++++++++++++++++++-- bdep/project.hxx | 72 ++++++++++++++++++++++++++++---- bdep/project.xml | 15 +++++-- bdep/types.hxx | 7 ++++ bdep/utility.cxx | 6 +-- bdep/utility.hxx | 4 +- 15 files changed, 467 insertions(+), 37 deletions(-) create mode 100644 bdep/database-views.hxx diff --git a/bdep/buildfile b/bdep/buildfile index cd7c690..abbd774 100644 --- a/bdep/buildfile +++ b/bdep/buildfile @@ -23,7 +23,8 @@ config-options \ init-options exe{bdep}: {hxx ixx txx cxx}{** -{$options_topics} -*-odb -version} \ - {hxx ixx cxx}{$options_topics} {hxx ixx cxx}{project-odb} \ + {hxx ixx cxx}{$options_topics} \ + {hxx ixx cxx}{project-odb database-views-odb} \ {hxx}{version} $libs hxx{version}: in{version} $src_root/file{manifest} diff --git a/bdep/config.cxx b/bdep/config.cxx index 59dcbad..f7d74ed 100644 --- a/bdep/config.cxx +++ b/bdep/config.cxx @@ -4,12 +4,92 @@ #include +#include +#include #include using namespace std; namespace bdep { + shared_ptr + cmd_config_add (const dir_path& prj, + database& db, + dir_path path, + optional name, + optional def, + optional id) + { + if (!exists (path)) + fail << "configuration directory " << path << " does not exist"; + + transaction t (db.begin ()); + + using count = configuration_count; + using query = bdep::query; + + // By default the first added configuration is the default. + // + if (!def) + def = (db.query_value () == 0); + + // Make sure the configuration path is absolute and normalized. Also + // derive relative to project directory path is possible. + // + path.complete (); + path.normalize (); + + optional rel_path; + try {rel_path = path.relative (prj);} catch (const invalid_path&) {} + + shared_ptr r ( + new configuration { + id, + name, + path, + move (rel_path), + *def}); + + try + { + db.persist (r); + } + catch (const odb::exception&) + { + // See if this is id, name, or path conflict. + // + if (id && db.query_value (query::id == *id) != 0) + fail << "configuration with id " << *id << " already exists " + << "in project " << prj; + + if (name && db.query_value (query::name == *name) != 0) + fail << "configuration with name '" << *name << "' already exists " + << "in project " << prj; + + if (db.query_value (query::path == path.string ()) != 0) + fail << "configuration with path " << path << " already exists " + << "in project " << prj; + + // Hm, what could that be? + // + throw; + } + + t.commit (); + + if (verb) + { + diag_record dr (text); + /* */ dr << "added configuration "; + if (r->name) dr << '@' << *r->name << ' '; + /* */ dr << r->path << " (" << *r->id; + if (r->default_) dr << ", default"; + /* */ dr << ')'; + } + + return r; + } + int cmd_config (const cmd_config_options& o, cli::scanner& args) { diff --git a/bdep/config.hxx b/bdep/config.hxx index e4244e6..2f04719 100644 --- a/bdep/config.hxx +++ b/bdep/config.hxx @@ -8,10 +8,19 @@ #include #include +#include #include namespace bdep { + shared_ptr + cmd_config_add (const dir_path& prj, + database&, + dir_path path, + optional name, + optional default_ = nullopt, + optional id = nullopt); + int cmd_config (const cmd_config_options&, cli::scanner& args); } diff --git a/bdep/database-views.hxx b/bdep/database-views.hxx new file mode 100644 index 0000000..286f7ca --- /dev/null +++ b/bdep/database-views.hxx @@ -0,0 +1,28 @@ +// file : bdep/database-views.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BDEP_DATABASE_VIEWS_HXX +#define BDEP_DATABASE_VIEWS_HXX + +#include + +#include +#include + +namespace bdep +{ + #pragma db view table("sqlite_master") + struct sqlite_master + { + string type; + string name; + string sql; + + #pragma db member(type) column("type") + #pragma db member(name) column("type") + #pragma db member(sql) column("sql") + }; +} + +#endif // BDEP_DATABASE_VIEWS_HXX diff --git a/bdep/database.cxx b/bdep/database.cxx index c456961..91907dc 100644 --- a/bdep/database.cxx +++ b/bdep/database.cxx @@ -9,6 +9,9 @@ #include +#include +#include + using namespace std; namespace bdep @@ -21,9 +24,11 @@ namespace bdep { tracer trace ("open"); - path f (d / bdep_dir / "bdep.sqlite3"); + path f (d / bdep_file); - if (!create && !exists (f)) + if (exists (f)) + create = false; + else if (!create) fail << d << " does not look like an initialized project directory" << info << "run 'bdep init' to initialize"; @@ -51,8 +56,9 @@ namespace bdep // try { - db.connection ()->execute ("PRAGMA locking_mode = EXCLUSIVE"); - transaction t (db.begin_exclusive ()); + connection_ptr c (db.connection ()); + c->execute ("PRAGMA locking_mode = EXCLUSIVE"); + transaction t (c->begin_exclusive ()); if (create) { @@ -62,6 +68,41 @@ namespace bdep fail << f << ": already has database schema"; schema_catalog::create_schema (db); + + // Make path comparison case-insensitive for certain platforms. + // + // For details on this technique see the SQLite's ALTER TABLE + // documentation. Note that here we don't increment SQLite's + // schema_version since we are in the same transaction as where we + // have created the schema. + // +#ifdef _WIN32 + db.execute ("PRAGMA writable_schema = ON"); + + const char* where ("type == 'table' AND name == 'configuration'"); + + sqlite_master t (db.query_value (where)); + + auto set_nocase = [&t] (const char* name) + { + string n ('\"' + string (name) + '\"'); + size_t p (t.sql.find (n)); + assert (p != string::npos); + p = t.sql.find_first_of (",)", p); + assert (p != string::npos); + t.sql.insert (p, " COLLATE NOCASE"); + }; + + set_nocase ("path"); + set_nocase ("relative_path"); + + db.execute ("UPDATE sqlite_master" + " SET sql = '" + t.sql + "'" + " WHERE " + where); + + db.execute ("PRAGMA writable_schema = OFF"); + db.execute ("PRAGMA integrity_check"); +#endif } else { diff --git a/bdep/database.hxx b/bdep/database.hxx index 76642bf..d90f88d 100644 --- a/bdep/database.hxx +++ b/bdep/database.hxx @@ -24,9 +24,6 @@ namespace bdep using odb::result; using odb::session; - using odb::sqlite::database; - using odb::sqlite::transaction; - database open (const dir_path& project, tracer&, bool create = false); diff --git a/bdep/init.cxx b/bdep/init.cxx index 1e850b8..2355498 100644 --- a/bdep/init.cxx +++ b/bdep/init.cxx @@ -4,7 +4,9 @@ #include +#include #include +#include #include using namespace std; @@ -16,18 +18,102 @@ namespace bdep { tracer trace ("init"); - //@@ TODO: validate project/config options for sub-modes. - //@@ TODO: print project/package(s) being initialized. - project_packages pp ( - find_project_packages (o, - o.empty () /* ignore_packages */)); + find_project_packages (o, o.empty () /* ignore_packages */)); - text << pp.project; + const dir_path& prj (pp.project); + text << prj; for (const dir_path& d: pp.packages) - text << " " << (pp.project / d); + text << " " << (prj / d); + + // Create .bdep/. + // + { + dir_path d (prj / bdep_dir); + + if (!exists (d)) + mk (prj / bdep_dir); + } + + // Open the database creating it if necessary. + // + database db (open (pp.project, trace, true /* create */)); + + // --empty + // + bool ca (o.config_add_specified ()); + bool cc (o.config_create_specified ()); + + if (o.empty ()) + { + if (ca) fail << "both --empty and --config-add specified"; + if (cc) fail << "both --empty and --config-create specified"; + + //@@ TODO: what should we do if the database already exists? + + return 0; + } + + // Make sure everyone refers to the same objects across all the + // transactions. + // + session s; + + // --config-add/create + // + configurations cfgs; + if (ca || cc) + { + const char* m (!ca ? "--config-create" : + !cc ? "--config-add" : + nullptr); + + if (m == nullptr) + fail << "both --config-add and --config-create specified"; + + optional name; + if (size_t n = o.config_name ().size ()) + { + if (n > 1) + fail << "multiple configuration names specified for " << m; + + name = o.config_name ()[0]; + } + + optional id; + if (size_t n = o.config_id ().size ()) + { + if (n > 1) + fail << "multiple configuration ids specified for " << m; + + id = o.config_id ()[0]; + } + + cfgs.push_back ( + ca + ? cmd_config_add (prj, + db, + o.config_add (), + move (name), + nullopt /* default */, // @@ TODO: --[no]-default + move (id)) + : nullptr); // @@ TODO: create + + // Fall through. + } + + transaction t (db.begin ()); + + // If this is the default mode, then find the configurations the user + // wants us to use. + // + if (cfgs.empty ()) + cfgs = find_configurations (prj, t, o); + + //@@ TODO: print project/package(s) being initialized. + t.commit (); return 0; } } diff --git a/bdep/odb.sh b/bdep/odb.sh index 77372b9..4bd83b5 100755 --- a/bdep/odb.sh +++ b/bdep/odb.sh @@ -16,3 +16,11 @@ $odb $lib -I.. -I../../libbpkg -I../../libbutl \ --hxx-prologue '#include ' \ --include-with-brackets --include-prefix bdep --guard-prefix BDEP \ --sqlite-override-null project.hxx + +$odb $lib -I.. -I../../libbpkg -I../../libbutl \ + -DLIBODB_BUILD2 -DLIBODB_SQLITE_BUILD2 \ + -d sqlite --std c++11 --generate-query \ + --odb-epilogue '#include ' \ + --hxx-prologue '#include ' \ + --include-with-brackets --include-prefix bdep --guard-prefix BDEP \ + --sqlite-override-null database-views.hxx diff --git a/bdep/project.cli b/bdep/project.cli index f080917..cbf33a3 100644 --- a/bdep/project.cli +++ b/bdep/project.cli @@ -13,16 +13,22 @@ namespace bdep // class project_options: common_options { + bool --all|-a + { + "Use all build configurations." + } + dir_paths --config|-c { "", "Specify the build configuration to use as a directory." } - bool --all|-a + vector --config-id { - "Use all build configurations." - } + "", + "Specify the build configuration to use as an id." + }; // Storage for configuration names specified as @. // diff --git a/bdep/project.cxx b/bdep/project.cxx index b7c1775..84b7acd 100644 --- a/bdep/project.cxx +++ b/bdep/project.cxx @@ -3,15 +3,113 @@ // license : MIT; see accompanying LICENSE file #include +#include #include +#include #include using namespace std; namespace bdep { + configurations + find_configurations (const dir_path& prj, + transaction& t, + const project_options& po) + { + configurations r; + + // Weed out duplicates. + // + auto add = [&r] (shared_ptr c) + { + if (find_if (r.begin (), + r.end (), + [&c] (const shared_ptr& e) + { + return *c->id == *e->id; + }) == r.end ()) + r.push_back (move (c)); + }; + + database& db (t.database ()); + using query = bdep::query; + + // @ + // + if (po.config_name_specified ()) + { + for (const string& n: po.config_name ()) + { + if (auto c = db.query_one (query::name == n)) + add (c); + else + fail << "no configuration name '" << n << "' in project " << prj; + } + } + + // --config + // + if (po.config_specified ()) + { + for (dir_path d: po.config ()) + { + d.complete (); + d.normalize (); + + if (auto c = db.query_one (query::path == d.string ())) + add (c); + else + fail << "no configuration directory " << d << " in project " << prj; + } + } + + // --config-id + // + if (po.config_id_specified ()) + { + for (uint64_t id: po.config_id ()) + { + if (auto c = db.find (id)) + add (c); + else + fail << "no configuration id " << id << " in project " << prj; + } + } + + // --all + // + if (po.all ()) + { + for (auto c: pointer_result (db.query ())) + add (c); + } + + // default + // + if (r.empty ()) + { + if (auto c = db.query_one (query::default_)) + add (c); + else + fail << "no default configuration in project " << prj << + info << "use (@ | --config|-c | --all|-a) to " + << "specify configuration explicitly"; + } + + // Validate all the returned configuration directories are still there. + // + for (const shared_ptr& c: r) + { + if (!exists (c->path)) + fail << "configuration directory " << c->path << " no longer exists"; + } + + return r; + } + // Given a directory which can a project root, a package root, or one of // their subdirectories, return the absolute project (first) and relative // package (second) directories. The package directory may be absent if the @@ -53,11 +151,13 @@ namespace bdep // Fall through (can also be the project root). } - // Check for configurations.manifest first since a simple project will - // have no packages.manifest + // Check for the database file first since an (initialized) simple + // project mosl likely won't have any *.manifest files. // - if (exists (d / configurations_file, true) || - exists (d / packages_file, true)) + if (exists (d / bdep_file, true) || + exists (d / packages_file, true) || + exists (d / repositories_file, true) || + exists (d / configurations_file, true)) { prj = move (d); break; diff --git a/bdep/project.hxx b/bdep/project.hxx index 274455c..42f520a 100644 --- a/bdep/project.hxx +++ b/bdep/project.hxx @@ -26,9 +26,16 @@ void assert (int); namespace bdep { + using optional_string = optional; + using optional_dir_path = optional; + #pragma db map type(dir_path) as(string) \ to((?).string ()) from(bdep::dir_path (?)) + #pragma db map type(optional_dir_path) as(bdep::optional_string) \ + to((?) ? (?)->string () : bdep::optional_string ()) \ + from((?) ? bdep::dir_path (*(?)) : bdep::optional_dir_path ()) + //@@ TODO: do we need session/shared_ptr? #pragma db object pointer(shared_ptr) session @@ -36,23 +43,72 @@ namespace bdep { public: - dir_path path; // @@ TODO: relative or absolute? - string name; + // The configuration stores an absolute and normalized path to the bpkg + // configuration. It may also have a name. A configuration can be moved or + // renamed so we also have the id (it is NULL'able so that we can assign + // fixed configuration ids, for example, in configurations.manifest). + // + // We also store a version of the configuration path relative to the + // project directory so that we can automatically handle moving of the + // project and its configurations as a bundle (note that the dir: + // repository path in the configuration will have to be adjust as well). + // Since it is not always possible to derive a relative path, it is + // optional. + // + optional id; + optional name; + dir_path path; + optional relative_path; + + bool default_; // Database mapping. // - #pragma db member(name) id - //#pragma db member(name) index + #pragma db member(id) id auto + #pragma db member(name) unique + #pragma db member(path) unique + + // Make path comparison case-insensitive for certain platforms. + // + // It would have been nice to do something like this but we can't: the + // options are stored in the changelog and this will render changelog + // platform-specific. So instead we tweak the scheme at runtime after + // creating the database. + // + // #ifdef _WIN32 + // #pragma db member(path) options("COLLATE NOCASE") + // #pragma db member(relative_path) options("COLLATE NOCASE") + // #endif private: friend class odb::access; configuration () = default; }; - // Given project_options (and CWD) locate the packages and their project. - // The result is the absolute and normalized project directory and a vector - // of relative (to the project directory) package directories (which will be - // empty if ignore_packages is true). + #pragma db view object(configuration) + struct configuration_count + { + #pragma db column("COUNT(*)") + size_t result; + + operator size_t () const {return result;} + }; + + // Given the project directory, database, and options resolve all the + // mentioned configurations or find the default configuration if none were + // mentioned. + // + using configurations = vector>; + + configurations + find_configurations (const dir_path& prj, + transaction&, + const project_options&); + + // Given the project options (and CWD) locate the packages and their + // project. The result is the absolute and normalized project directory and + // a vector of relative (to the project directory) package directories + // (which will be empty if ignore_packages is true). // // Note that if the package directory is the same as project, then the // package directory will be empty (and not ./). diff --git a/bdep/project.xml b/bdep/project.xml index c3991bc..cf8d2c7 100644 --- a/bdep/project.xml +++ b/bdep/project.xml @@ -1,11 +1,20 @@ - + - - + + + + + + + + + + +
diff --git a/bdep/types.hxx b/bdep/types.hxx index c4aa9b5..4f3ec13 100644 --- a/bdep/types.hxx +++ b/bdep/types.hxx @@ -20,6 +20,8 @@ #include // logic_error, invalid_argument, runtime_error #include +#include + #include #include #include @@ -62,6 +64,11 @@ namespace bdep using std::system_error; using io_error = std::ios_base::failure; + // + // + using odb::sqlite::database; + using odb::sqlite::transaction; + // // using butl::optional; diff --git a/bdep/utility.cxx b/bdep/utility.cxx index 93f0458..0ed41b9 100644 --- a/bdep/utility.cxx +++ b/bdep/utility.cxx @@ -4,8 +4,6 @@ #include -#include // cout, cin - #include #include @@ -20,10 +18,12 @@ namespace bdep const path empty_path; const dir_path empty_dir_path; - const dir_path bdep_dir (".bdep"); + const dir_path bdep_dir (".bdep"); + const path bdep_file (bdep_dir / "bdep.sqlite3"); const path manifest_file ("manifest"); const path packages_file ("packages.manifest"); + const path repositories_file ("repositories.manifest"); const path configurations_file ("configurations.manifest"); bool diff --git a/bdep/utility.hxx b/bdep/utility.hxx index 54e17fb..af85355 100644 --- a/bdep/utility.hxx +++ b/bdep/utility.hxx @@ -56,10 +56,12 @@ namespace bdep // Widely-used paths. // - extern const dir_path bdep_dir; // .bdep/ + extern const dir_path bdep_dir; // .bdep/ + extern const path bdep_file; // .bdep/bdep.sqlite3 extern const path manifest_file; // manifest extern const path packages_file; // packages.manifest + extern const path repositories_file; // repositories.manifest extern const path configurations_file; // configurations.manifest // Directory extracted from argv[0] (i.e., this process' recall directory) -- cgit v1.1