aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2018-03-09 15:06:27 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2018-03-09 15:06:27 +0200
commitd304762e4338b4a055e2be018205a00c2ca9ee2f (patch)
tree252a4aa70118135e589095442fb4df3879c96eb0
parentdf5e58e6e5eb2727a185bf9a98a462c18fa3a83d (diff)
Support for resolving and adding configurations
-rw-r--r--bdep/buildfile3
-rw-r--r--bdep/config.cxx80
-rw-r--r--bdep/config.hxx9
-rw-r--r--bdep/database-views.hxx28
-rw-r--r--bdep/database.cxx49
-rw-r--r--bdep/database.hxx3
-rw-r--r--bdep/init.cxx100
-rwxr-xr-xbdep/odb.sh8
-rw-r--r--bdep/project.cli12
-rw-r--r--bdep/project.cxx108
-rw-r--r--bdep/project.hxx72
-rw-r--r--bdep/project.xml15
-rw-r--r--bdep/types.hxx7
-rw-r--r--bdep/utility.cxx6
-rw-r--r--bdep/utility.hxx4
15 files changed, 467 insertions, 37 deletions
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 <bdep/config.hxx>
+#include <bdep/database.hxx>
+#include <bdep/project-odb.hxx>
#include <bdep/diagnostics.hxx>
using namespace std;
namespace bdep
{
+ shared_ptr<configuration>
+ cmd_config_add (const dir_path& prj,
+ database& db,
+ dir_path path,
+ optional<string> name,
+ optional<bool> def,
+ optional<uint64_t> id)
+ {
+ if (!exists (path))
+ fail << "configuration directory " << path << " does not exist";
+
+ transaction t (db.begin ());
+
+ using count = configuration_count;
+ using query = bdep::query<count>;
+
+ // By default the first added configuration is the default.
+ //
+ if (!def)
+ def = (db.query_value<count> () == 0);
+
+ // Make sure the configuration path is absolute and normalized. Also
+ // derive relative to project directory path is possible.
+ //
+ path.complete ();
+ path.normalize ();
+
+ optional<dir_path> rel_path;
+ try {rel_path = path.relative (prj);} catch (const invalid_path&) {}
+
+ shared_ptr<configuration> 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<count> (query::id == *id) != 0)
+ fail << "configuration with id " << *id << " already exists "
+ << "in project " << prj;
+
+ if (name && db.query_value<count> (query::name == *name) != 0)
+ fail << "configuration with name '" << *name << "' already exists "
+ << "in project " << prj;
+
+ if (db.query_value<count> (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 <bdep/types.hxx>
#include <bdep/utility.hxx>
+#include <bdep/project.hxx>
#include <bdep/config-options.hxx>
namespace bdep
{
+ shared_ptr<configuration>
+ cmd_config_add (const dir_path& prj,
+ database&,
+ dir_path path,
+ optional<string> name,
+ optional<bool> default_ = nullopt,
+ optional<uint64_t> 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 <odb/core.hxx>
+
+#include <bdep/types.hxx>
+#include <bdep/utility.hxx>
+
+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 <bdep/diagnostics.hxx>
+#include <bdep/database-views.hxx>
+#include <bdep/database-views-odb.hxx>
+
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<sqlite_master> (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 <bdep/init.hxx>
+#include <bdep/config.hxx>
#include <bdep/project.hxx>
+#include <bdep/database.hxx>
#include <bdep/diagnostics.hxx>
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<string> 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<uint64_t> 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 <bdep/wrapper-traits.hxx>' \
--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 <bdep/wrapper-traits.hxx>' \
+ --hxx-prologue '#include <bdep/wrapper-traits.hxx>' \
+ --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
{
"<dir>",
"Specify the build configuration to use as a directory."
}
- bool --all|-a
+ vector<uint64_t> --config-id
{
- "Use all build configurations."
- }
+ "<num>",
+ "Specify the build configuration to use as an id."
+ };
// Storage for configuration names specified as @<cfg-name>.
//
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 <bdep/project.hxx>
+#include <bdep/project-odb.hxx>
#include <libbpkg/manifest.hxx>
+#include <bdep/database.hxx>
#include <bdep/diagnostics.hxx>
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<configuration> c)
+ {
+ if (find_if (r.begin (),
+ r.end (),
+ [&c] (const shared_ptr<configuration>& e)
+ {
+ return *c->id == *e->id;
+ }) == r.end ())
+ r.push_back (move (c));
+ };
+
+ database& db (t.database ());
+ using query = bdep::query<configuration>;
+
+ // @<cfg-name>
+ //
+ if (po.config_name_specified ())
+ {
+ for (const string& n: po.config_name ())
+ {
+ if (auto c = db.query_one<configuration> (query::name == n))
+ add (c);
+ else
+ fail << "no configuration name '" << n << "' in project " << prj;
+ }
+ }
+
+ // --config <cfg-dir>
+ //
+ if (po.config_specified ())
+ {
+ for (dir_path d: po.config ())
+ {
+ d.complete ();
+ d.normalize ();
+
+ if (auto c = db.query_one<configuration> (query::path == d.string ()))
+ add (c);
+ else
+ fail << "no configuration directory " << d << " in project " << prj;
+ }
+ }
+
+ // --config-id <cfg-num>
+ //
+ if (po.config_id_specified ())
+ {
+ for (uint64_t id: po.config_id ())
+ {
+ if (auto c = db.find<configuration> (id))
+ add (c);
+ else
+ fail << "no configuration id " << id << " in project " << prj;
+ }
+ }
+
+ // --all
+ //
+ if (po.all ())
+ {
+ for (auto c: pointer_result (db.query<configuration> ()))
+ add (c);
+ }
+
+ // default
+ //
+ if (r.empty ())
+ {
+ if (auto c = db.query_one<configuration> (query::default_))
+ add (c);
+ else
+ fail << "no default configuration in project " << prj <<
+ info << "use (@<cfg-name> | --config|-c <cfg-dir> | --all|-a) to "
+ << "specify configuration explicitly";
+ }
+
+ // Validate all the returned configuration directories are still there.
+ //
+ for (const shared_ptr<configuration>& 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<string>;
+ using optional_dir_path = optional<dir_path>;
+
#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<uint64_t> id;
+ optional<string> name;
+ dir_path path;
+ optional<dir_path> 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<shared_ptr<configuration>>;
+
+ 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 @@
<changelog xmlns="http://www.codesynthesis.com/xmlns/odb/changelog" database="sqlite" version="1">
<model version="1">
<table name="configuration" kind="object">
- <column name="path" type="TEXT" null="true"/>
+ <column name="id" type="INTEGER" null="true"/>
<column name="name" type="TEXT" null="true"/>
- <primary-key>
- <column name="name"/>
+ <column name="path" type="TEXT" null="true"/>
+ <column name="relative_path" type="TEXT" null="true"/>
+ <column name="default" type="INTEGER" null="true"/>
+ <primary-key auto="true">
+ <column name="id"/>
</primary-key>
+ <index name="configuration_name_i" type="UNIQUE">
+ <column name="name"/>
+ </index>
+ <index name="configuration_path_i" type="UNIQUE">
+ <column name="path"/>
+ </index>
</table>
</model>
</changelog>
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 <stdexcept> // logic_error, invalid_argument, runtime_error
#include <system_error>
+#include <odb/sqlite/forward.hxx>
+
#include <libbutl/path.mxx>
#include <libbutl/optional.mxx>
#include <libbutl/fdstream.mxx>
@@ -62,6 +64,11 @@ namespace bdep
using std::system_error;
using io_error = std::ios_base::failure;
+ // <odb/sqlite/forward.hxx>
+ //
+ using odb::sqlite::database;
+ using odb::sqlite::transaction;
+
// <libbutl/optional.mxx>
//
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 <bdep/utility.hxx>
-#include <iostream> // cout, cin
-
#include <libbutl/process.mxx>
#include <libbutl/fdstream.mxx>
@@ -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)