From 54e3c33fb327efe0cbfd806c5468cbe390dafeaa Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 12 Jun 2015 12:32:06 +0200 Subject: Implement object model for packages --- brep/package | 367 ++++++++++++++++++++++++++++++++++++++++++++++++++ brep/package.cxx | 21 +++ brep/search | 3 + brep/search.cxx | 70 ++++++++++ brep/view | 3 + brep/view.cxx | 87 ++++++++++++ build.sh | 5 +- build/root.build | 1 + buildfile | 2 +- etc/config | 6 +- etc/pgctl | 62 ++++++--- www/htdocs/index.html | 28 +++- 12 files changed, 629 insertions(+), 26 deletions(-) create mode 100644 brep/package create mode 100644 brep/package.cxx diff --git a/brep/package b/brep/package new file mode 100644 index 0000000..1ad11b2 --- /dev/null +++ b/brep/package @@ -0,0 +1,367 @@ +// file : brep/package -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BPKG_PACKAGE +#define BPKG_PACKAGE + +#include +#include +#include +#include // shared_ptr +#include // pair + +#include +#include // database +#include +#include + +namespace brep +{ + // @@ If namespace, then should probably call it 'repo'. + // + // @@ Should probably use optional from libbutl instead of odb::nullable + // for consistency. Create butl profile? + // + // @@ Might make sense to put some heavy members (e.g., description, + // containers) into a separate section. + // + // @@ Not sure there is a benefit in making tags a full-blown container + // (i.e., a separate table). Maybe provide a mapping of vector + // to TEXT as a comma-separated list. + // + + // Forward declarations. + // + struct package; + struct package_version; + + using strings = std::vector; + + #pragma db value + struct package_version_id + { + std::string package; + std::string version; + + // Database mapping. + // + #pragma db member(package) points_to(package) + }; + + inline bool + operator< (const package_version_id& x, const package_version_id& y) + { + int r (x.package.compare (y.package)); + return r < 0 || r == 0 && x.version < y.version; + } + + #pragma db value + class priority + { + public: + enum value_type {low, medium, high, security}; + + value_type value; // Shouldn't be necessary to access directly. + std::string comment; + + priority (value_type v = low): value (v) {} + operator value_type () const {return value;} + + // Database mapping. + // + #pragma db member(value) column("") + }; + + #pragma db value + struct url: std::string + { + std::string comment; + + // Database mapping. + // + #pragma db member(value) virtual(std::string) before \ + access(this) column("") + }; + + #pragma db value + struct email: std::string + { + std::string comment; + + // Database mapping. + // + #pragma db member(value) virtual(std::string) before \ + access(this) column("") + }; + + // licenses + // + #pragma db value + struct licenses: strings + { + std::string comment; + }; + + using license_alternatives = std::vector; + + // dependencies + // + enum class comparison {eq, lt, gt, le, ge}; + + #pragma db value + struct version_comparison + { + std::string value; + comparison operation; + }; + + #pragma db value + struct dependency + { + using package_type = brep::package; + + // Notes: + // + // 1. Will the package be always resolvable? What if it is in + // another repository (i.e., a "chained" third-party repo). + // The question is then whether we will load such "third- + // party packages" (i.e., packages that are not in our + // repository). If the answer is yes, then we can have + // a pointer here. If the answer is no, then we can't. + // Also, if the answer is yes, we probably don't need to + // load as much information as for "our own" packages. We + // also shouldn't be showing them in search results, etc. + // I think all we need is to know which repository this + // package comes from so that we can tell the user. How are + // we going to capture this? Poly hierarchy of packages? + // + // 2. I believe we don't need to use a weak pointer here since + // there should be no package dependency cycles (and therefore + // ownership cycles). + // + // 3. Actually there can be dependency cycle as dependency referes not to + // just a package but a specific version, so for the same pair of + // packages dependency for different versions can have an opposite + // directions. The possible solution is instead of a package we point + // to the earliest version that satisfies the condition. But this + // approach requires to ensure no cycles exist before instantiating + // package objects which in presense of "foreign" packages can be + // tricky. Can stick to just a package name until get some clarity on + // "foreign" package resolution. + // + std::string package; + odb::nullable version; + + // Database mapping. + // + #pragma db member(package) not_null + }; + + #pragma db value + struct dependency_alternatives: std::vector + { + bool conditional; + std::string comment; + }; + + using dependencies = std::vector; + + // requirements + // + #pragma db value + struct requirement_alternatives: strings + { + bool conditional; + std::string comment; + }; + + using requirements = std::vector; + + #pragma db object pointer(std::shared_ptr) session + struct package + { + // Manifest data. + // + using url_type = brep::url; + using email_type = brep::email; + + std::string name; + std::string summary; + std::string tags; + std::string description; + url_type url; + odb::nullable package_url; + email_type email; + odb::nullable package_email; + std::vector> versions; + + // Additional data. + // + + // Database mapping. + // + #pragma db member(name) id + #pragma db member(versions) inverse(id.package) + }; + + #pragma db object pointer(std::shared_ptr) session + struct package_version + { + // Manifest data. + // + using package_type = brep::package; + using priority_type = brep::priority; + using license_alternatives_type = brep::license_alternatives; + using dependencies_type = brep::dependencies; + using requirements_type = brep::requirements; + + std::string version; + std::shared_ptr package; + priority_type priority; + license_alternatives_type license_alternatives; + std::string changes; + dependencies_type dependencies; + requirements_type requirements; + + // Additional data. + // + std::string repository; // E.g., "stable", "testing". + + // Database mapping. + // + + // id + // + package_version_id + id () const {return package_version_id {package->name, version};} + + void + id (const package_version_id&, odb::database&); + + #pragma db member(version) transient + #pragma db member(package) transient + #pragma db member(id) virtual(package_version_id) before id \ + get(id) set(id ((?), (!))) column("") + + // license + // + using _license_key = std::pair; + using _licenses_type = std::map<_license_key, std::string>; + + #pragma db value(_license_key) + #pragma db member(_license_key::first) column("alternative") + #pragma db member(_license_key::second) column("index") + + #pragma db member(license_alternatives) id_column("") value_column("") + #pragma db member(licenses) \ + virtual(_licenses_type) \ + after(license_alternatives) \ + get(_get (this.license_alternatives)) \ + set(_set (this.license_alternatives, (?))) \ + id_column("") key_column("") value_column("license") + + // dependencies + // + using _dependency_key = std::pair; + using _dependency_alternatives_type = + std::map<_dependency_key, dependency>; + + #pragma db value(_dependency_key) + #pragma db member(_dependency_key::first) column("dependency") + #pragma db member(_dependency_key::second) column("index") + + #pragma db member(dependencies) id_column("") value_column("") + #pragma db member(dependency_alternatives) \ + virtual(_dependency_alternatives_type) \ + after(dependencies) \ + get(_get (this.dependencies)) \ + set(_set (this.dependencies, (?))) \ + id_column("") key_column("") value_column("dep_") + + // requirements + // + using _requirement_key = std::pair; + using _requirement_alternatives_type = + std::map<_requirement_key, std::string>; + + #pragma db value(_requirement_key) + #pragma db member(_requirement_key::first) column("requirement") + #pragma db member(_requirement_key::second) column("index") + + #pragma db member(requirements) id_column("") value_column("") + #pragma db member(requirement_alternatives) \ + virtual(_requirement_alternatives_type) \ + after(requirements) \ + get(_get (this.requirements)) \ + set(_set (this.requirements, (?))) \ + id_column("") key_column("") value_column("id") + }; +} + +// Nested container emulation support for ODB. +// +// Note that the outer index in the inner container should strictly +// speaking be a foreign key pointing to the index of the outer +// container. The only way to achieve this currently is to manually +// add the constraint via ALTER TABLE ADD CONSTRAINT. Note, however, +// that as long as we only modify these tables via the ODB container +// interface, not having the foreign key (and not having ON DELETE +// CASCADE) should be harmless (since we have a foreign key pointing +// to the object id). +// +#include +#include +#include // size_t +#include // pair, declval() +#include +#include // remove_reference + +namespace odb +{ + template + struct _inner: std::remove_reference ()[0])> {}; + + template + std::map, typename _inner::type> + _get (const std::vector& v) + { + using namespace std; + + using I = typename _inner::type; + using key = pair; + + map r; + for (size_t n (0); n != v.size (); ++n) + { + const O& o (v[n]); + for (size_t m (0); m != o.size (); ++m) + r.emplace (key (n, m), o[m]); + } + return r; + } + + //@@ Second argument should be && once ODB uses move(). + // + template + void + _set (std::vector& v, std::map, I>& r) + { + using namespace std; + + using key = pair; + + for (auto& p: r) + { + size_t n (p.first.first); + size_t m (p.first.second); + I& i (p.second); + + assert (n < v.size ()); + assert (m == v[n].size ()); + v[n].push_back (std::move (i)); + } + } +} + +#endif // BPKG_PACKAGE diff --git a/brep/package.cxx b/brep/package.cxx new file mode 100644 index 0000000..9331f55 --- /dev/null +++ b/brep/package.cxx @@ -0,0 +1,21 @@ +// file : brep/package.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include + +namespace brep +{ + // package_version + // + void package_version:: + id (const package_version_id& v, odb::database& db) + { + version = v.version; + package = db.load (v.package); + } +} diff --git a/brep/search b/brep/search index 9670899..078922e 100644 --- a/brep/search +++ b/brep/search @@ -7,6 +7,8 @@ #include // shared_ptr +#include + #include #include @@ -23,6 +25,7 @@ namespace brep private: std::shared_ptr options_; + std::shared_ptr db_; }; } diff --git a/brep/search.cxx b/brep/search.cxx index 3f50383..9fc37e2 100644 --- a/brep/search.cxx +++ b/brep/search.cxx @@ -8,9 +8,19 @@ #include #include +#include +#include +#include + +#include + #include +#include +#include + using namespace std; +using namespace odb::core; namespace brep { @@ -23,6 +33,12 @@ namespace brep cli::unknown_mode::fail, cli::unknown_mode::fail); + db_ = make_shared("", + "", + "brep", + options_->db_host (), + options_->db_port ()); + if (options_->results_on_page () > 30) fail << "too many search results on page: " << options_->results_on_page (); @@ -36,6 +52,60 @@ namespace brep { MODULE_DIAG; + std::shared_ptr cli (make_shared ()); + + cli->name = "cli"; + cli->summary = "CLI is ..."; + cli->description = "This is CLI"; + cli->tags = "compiler, C++"; + + licenses l; + l.comment = "License\"A'"; + l.push_back ("XXX"); + l.push_back ("AAA"); + l.push_back ("BBB"); + l.push_back ("CCC"); + + std::shared_ptr v (make_shared ()); + + v->version = "1.1"; + v->package = cli; + + v->license_alternatives.push_back (l); + + dependency_alternatives da; + da.push_back ({"icl", version_comparison{"1.3.3", comparison::gt}}); + da.push_back ({"ocl", version_comparison{"1.5.5", comparison::lt}}); + + v->dependencies.push_back (da); + + { + requirement_alternatives ra; + ra.push_back ("TAO"); + ra.push_back ("ORBacus"); + + v->requirements.push_back (ra); + } + + { + requirement_alternatives ra; + ra.push_back ("Xerces"); + + v->requirements.push_back (ra); + } + + cli->versions.push_back (v); + + transaction t (db_->begin ()); +// t.tracer (odb::stderr_full_tracer); + + { + db_->persist (cli); + db_->persist (v); + } + + t.commit (); + chrono::seconds ma (60); rs.cookie ("Oh", " Ah\n\n", &ma, "/"); rs.cookie ("Hm", ";Yes", &ma); diff --git a/brep/view b/brep/view index 9b3030e..69c7d3c 100644 --- a/brep/view +++ b/brep/view @@ -7,6 +7,8 @@ #include // shared_ptr +#include + #include #include @@ -23,6 +25,7 @@ namespace brep private: std::shared_ptr options_; + std::shared_ptr db_; }; } diff --git a/brep/view.cxx b/brep/view.cxx index 0ec6c67..48c8188 100644 --- a/brep/view.cxx +++ b/brep/view.cxx @@ -7,10 +7,21 @@ #include // shared_ptr, make_shared() #include +#include +#include +#include + +#include + #include +#include +#include + using namespace std; +using namespace odb::core; +#pragma db namespace session namespace brep { void view:: @@ -19,11 +30,29 @@ namespace brep options_ = make_shared (s, cli::unknown_mode::fail, cli::unknown_mode::fail); + + db_ = make_shared("", + "", + "brep", + options_->db_host (), + options_->db_port ()); } void view:: handle (request& rq, response& rs) { + session s; + transaction t (db_->begin ()); + + shared_ptr p (db_->load ("cli")); + + for (auto& vp : p->versions) + { + s.cache_insert (*db_, vp.object_id (), vp.load ()); + } + + t.commit (); + ostream& o (rs.content (200, "text/html;charset=utf-8", false)); o << ""; @@ -40,6 +69,64 @@ namespace brep o << "
\n" << c.name << "=" << c.value; } + o << "

\n" << p->name << ": " << p->versions.size (); + + for (const auto& vp : p->versions) + { + shared_ptr v ( + s.cache_find (*db_, vp.object_id ())); + + if (!v) + { + o << "
no version in cache !"; + } + else + { + o << "
licenses:" << v->license_alternatives.size (); + + for (const auto& la : v->license_alternatives) + { + o << "
"; + + for (const auto& l : la) + { + o << " |" << l << "|"; + } + } + + o << "
deps:" << v->dependencies.size (); + + for (const auto& da : v->dependencies) + { + o << "
"; + + for (const auto& d : da) + { + o << " |" << d.package; + + if (!d.version.null ()) + { + o << "," << d.version->value << "," + << static_cast (d.version->operation) << "|"; + } + } + } + + o << "
requirements:" << v->requirements.size (); + + for (const auto& ra : v->requirements) + { + o << "
"; + + for (const auto& r : ra) + { + o << " |" << r << "|"; + } + } + } + + } + o << "

Search" << ""; } diff --git a/build.sh b/build.sh index 2a39f48..ef4e11a 100755 --- a/build.sh +++ b/build.sh @@ -3,5 +3,8 @@ DEBUG="-g -ggdb -fno-inline" cd ./brep; cli --generate-file-scanner --suppress-usage --hxx-suffix "" \ --option-prefix "" ./options.cli; cd .. +cd ./brep; odb -d pgsql --std c++11 --generate-query --generate-schema \ + package; cd .. + g++ -shared $DEBUG -std=c++11 -I. -I/usr/include/apr-1 \ - -fPIC -o libbrep.so `find . -name '*.cxx'` + -fPIC -o libbrep.so `find . -name '*.cxx'` -lodb-pgsql -lodb diff --git a/build/root.build b/build/root.build index d9c5766..bfa96d0 100644 --- a/build/root.build +++ b/build/root.build @@ -2,3 +2,4 @@ using cxx cxx.std = 11 cxx.poptions += -I$src_root -I/usr/include/apr-1 +cxx.libs = -lodb-pgsql -lodb diff --git a/buildfile b/buildfile index 6180ebf..21ed86f 100644 --- a/buildfile +++ b/buildfile @@ -1,4 +1,4 @@ -brep=brep/{diagnostics module options search view} +brep=brep/{diagnostics module options package package-odb search view} web=web/apache/{request service} libso{brep}: cxx{$brep $web services} diff --git a/etc/config b/etc/config index a1e851c..31a03a2 100644 --- a/etc/config +++ b/etc/config @@ -6,17 +6,17 @@ WORKSPACE_DIR="$PROJECT_DIR/var" # PostgreSQL settings (used in pgctl) PG_PORT=8432 +PG_SCHEMA_DIR="$PROJECT_DIR/brep" PG_DATA_DIR="$WORKSPACE_DIR/lib/pgsql" -PG_SOCKET_DIR="$WORKSPACE_DIR/run/pgsql" PG_LOG_DIR="$WORKSPACE_DIR/log/pgsql" -PG_OUT_FILE="$WORKSPACE_DIR/run/pgsql/out" +PG_WORKSPACE_DIR="$WORKSPACE_DIR/run/pgsql" # Apache settings (used in apachectl) AP_PORT=8080 AP_SERVER_NAME="cppget.org:$AP_PORT" AP_ADMIN_EMAIL=admin@cppget.org AP_LOG_LEVEL=trace1 -AP_DB_HOST="$PG_SOCKET_DIR" +AP_DB_HOST="$PG_WORKSPACE_DIR" AP_DB_PORT=$PG_PORT AP_MODULE_DIR="$PROJECT_DIR" AP_WWW_DIR="$PROJECT_DIR/www" diff --git a/etc/pgctl b/etc/pgctl index 26ad888..4331ca1 100755 --- a/etc/pgctl +++ b/etc/pgctl @@ -7,11 +7,13 @@ . `dirname $0`/config -cmd="$1" +CMD="$1" +SOCKET_DIR="$PG_WORKSPACE_DIR" +OUT_FILE="$PG_WORKSPACE_DIR/out" # Print usage description and exit. # -case $cmd in +case $CMD in init|start|stop|status|connect) ;; *) echo "Usage: pgctl (init|start|stop|status|connect)" @@ -21,10 +23,10 @@ esac ERROR=0 -# Initilization includes creating PostgreSQL DB cluster, creating and -# populating brep DB. +# Initialization includes creating PostgreSQL DB cluster, creating brep DB +# and schema. # -if test "$cmd" = "init"; then +if test "$CMD" = "init"; then if test -d "$PG_DATA_DIR"; then echo "PostgreSQL DB cluster directory $PG_DATA_DIR already exist" else @@ -41,7 +43,7 @@ if test "$cmd" = "init"; then fi fi -case $cmd in +case $CMD in start|init) # Start DB server if not running yet. # @@ -57,21 +59,21 @@ case $cmd in echo "PostgreSQL server starting ..." - mkdir -p `dirname $PG_OUT_FILE` + mkdir -p `dirname $OUT_FILE` mkdir -p "$PG_LOG_DIR" - mkdir -p "$PG_SOCKET_DIR" + mkdir -p "$SOCKET_DIR" pg_ctl start -D "$PG_DATA_DIR" -w -o \ - "-c port=$PG_PORT -c unix_socket_directories=$PG_SOCKET_DIR \ + "-c port=$PG_PORT -c unix_socket_directories=$SOCKET_DIR \ -c logging_collector=on -c log_directory=$PG_LOG_DIR" \ - 1>"$PG_OUT_FILE" 2>&1 + 1>"$OUT_FILE" 2>&1 ERROR=$? if test $ERROR -eq 0; then echo "server started" else - cat "$PG_OUT_FILE" 1>&2 + cat "$OUT_FILE" 1>&2 echo "server starting failed" exit $ERROR fi @@ -104,15 +106,14 @@ case $cmd in connect) echo "connecting to PostgreSQL server ..." - psql --host=$PG_SOCKET_DIR --port=$PG_PORT brep + psql --host=$SOCKET_DIR --port=$PG_PORT brep ;; esac -if test "$cmd" = "init"; then +if test "$CMD" = "init"; then # Create brep DB if not exist. # - psql --host=$PG_SOCKET_DIR --port=$PG_PORT -c "" brep \ - 1>>"$PG_OUT_FILE" 2>&1 + psql --host=$SOCKET_DIR --port=$PG_PORT -c "" brep 1>/dev/null 2>&1 ERROR=$? @@ -121,7 +122,7 @@ if test "$cmd" = "init"; then else if test $ERROR -eq 2; then echo "creating brep DB ..." - createdb --host=$PG_SOCKET_DIR --port=$PG_PORT brep + createdb --host=$SOCKET_DIR --port=$PG_PORT brep ERROR=$? if test $ERROR -eq 0; then @@ -136,8 +137,29 @@ if test "$cmd" = "init"; then fi fi - # Populate brep DB if required. - # psql --host=$PG_SOCKET_DIR --port=$PG_PORT -c "select count(1) ..." brep \ - # 1>>"$PG_OUT_FILE" 2>&1 - # ... + # Create brep DB schema if not exist. + # + psql --host=$SOCKET_DIR --port=$PG_PORT -c "select count(1) from package" \ + brep 1>/dev/null 2>&1 + + ERROR=$? + + if test $ERROR -eq 0; then + echo "brep DB schema already exist" + else + echo "creating brep DB schema ..." + + psql -v ON_ERROR_STOP=1 --host=$SOCKET_DIR --port=$PG_PORT \ + --file="$PG_SCHEMA_DIR/package.sql" brep 1>"$OUT_FILE" 2>&1 + + ERROR=$? + + if test $ERROR -eq 0; then + echo "brep DB schema created" + else + cat "$OUT_FILE" 1>&2 + echo "brep DB schema creating failed" + exit $ERROR; + fi + fi fi diff --git a/www/htdocs/index.html b/www/htdocs/index.html index 3d6d51e..d8c1185 100644 --- a/www/htdocs/index.html +++ b/www/htdocs/index.html @@ -1,5 +1,31 @@ + + + + + - Home Page +

Home Page

+ build2 is a build system aimed at improving cross-platform C++ software building and library use. It will be accompanied by a C++ package manager and a central repository of open source C++ libraries. + build2 is a build system aimed build2 is a build system aimed at improving cross-platform C++ software building and library use. It will be accompanied by a C++ package manager and a central repository of open source C++ libraries. + build2 is a build system aimed at improving cross-platform C++ at improving cross-platform C++ at improving cross-platform C++ software building and library use. It will be accompanied by a C++ package manager and a central repository of open source C++ libraries. + build2 is a build system aimed at ddw improving cross-platform C++ software building and library use. It will be accompanied by a C++ package manager and a central repository of open source C++ libraries. + build2 is a build system aimed at improving cross-platform C++ software building and library use. It will be accompanied by a C++ package manager and a central repository of open source C++ libraries. + build2 is a build system aimed at improving cross-platform C++ software building and library use. It will be accompanied by a C++ package manager and a central repository of open source C++ libraries. + build2 is a build sdhjsd system aimed at improving cross-platform C++ software building and library use. It will be accompanied by a C++ package manager and a central repository of open source C++ libraries. + build2 is a build system aimed at rkdf improving cross-platform C++ software building and library use. It will be accompanied by a C++ package manager and a central repository of open source C++ libraries. -- cgit v1.1