aboutsummaryrefslogtreecommitdiff
path: root/clean/clean.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'clean/clean.cxx')
-rw-r--r--clean/clean.cxx287
1 files changed, 287 insertions, 0 deletions
diff --git a/clean/clean.cxx b/clean/clean.cxx
new file mode 100644
index 0000000..fbb4d23
--- /dev/null
+++ b/clean/clean.cxx
@@ -0,0 +1,287 @@
+// file : clean/clean.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <set>
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <odb/pgsql/database.hxx>
+
+#include <libbutl/pager.hxx>
+
+#include <libbbot/build-config.hxx>
+
+#include <libbrep/build.hxx>
+#include <libbrep/build-odb.hxx>
+#include <libbrep/package.hxx>
+#include <libbrep/package-odb.hxx>
+#include <libbrep/database-lock.hxx>
+
+#include <clean/clean-options.hxx>
+
+using namespace std;
+using namespace bbot;
+using namespace brep;
+using namespace odb::core;
+
+// Operation failed, diagnostics has already been issued.
+//
+struct failed {};
+
+static const char* help_info (
+ " info: run 'brep-clean --help' for more information");
+
+int
+main (int argc, char* argv[])
+try
+{
+ cli::argv_scanner scan (argc, argv, true);
+ options ops (scan);
+
+ // Version.
+ //
+ if (ops.version ())
+ {
+ cout << "brep-clean " << BREP_VERSION_ID << endl
+ << "libbrep " << LIBBREP_VERSION_ID << endl
+ << "libbbot " << LIBBBOT_VERSION_ID << endl
+ << "libbpkg " << LIBBPKG_VERSION_ID << endl
+ << "libbutl " << LIBBUTL_VERSION_ID << endl
+ << "Copyright (c) 2014-2017 Code Synthesis Ltd" << endl
+ << "This is free software released under the MIT license." << endl;
+
+ return 0;
+ }
+
+ // Help.
+ //
+ if (ops.help ())
+ {
+ butl::pager p ("brep-clean help",
+ false,
+ ops.pager_specified () ? &ops.pager () : nullptr,
+ &ops.pager_option ());
+
+ print_usage (p.stream ());
+
+ // If the pager failed, assume it has issued some diagnostics.
+ //
+ return p.wait () ? 0 : 1;
+ }
+
+ const toolchain_timeouts& timeouts (ops.stale_timeout ());
+
+ auto i (timeouts.find (string ()));
+ timestamp default_timeout (i != timeouts.end ()
+ ? i->second
+ : timestamp_nonexistent);
+
+ // Load configurations names.
+ //
+ if (!scan.more ())
+ {
+ cerr << "error: configuration file expected" << endl
+ << help_info << endl;
+ return 1;
+ }
+
+ set<string> configs;
+ for (auto& c: parse_buildtab (path (scan.next ())))
+ configs.emplace (move (c.name));
+
+ if (scan.more ())
+ {
+ cerr << "error: unexpected argument encountered" << endl
+ << help_info << endl;
+ return 1;
+ }
+
+ odb::pgsql::database build_db (
+ ops.build_db_user (),
+ ops.build_db_password (),
+ ops.build_db_name (),
+ ops.build_db_host (),
+ ops.build_db_port (),
+ "options='-c default_transaction_isolation=serializable'");
+
+ odb::pgsql::database package_db (
+ ops.package_db_user (),
+ ops.package_db_password (),
+ ops.package_db_name (),
+ ops.package_db_host (),
+ ops.package_db_port (),
+ "options='-c default_transaction_isolation=serializable'");
+
+ // Prevent several brep-clean/migrate instances from updating build database
+ // simultaneously.
+ //
+ database_lock l (build_db);
+
+ // Check that the build and package database schemas match the current ones.
+ //
+ const string bs ("build");
+ if (schema_catalog::current_version (build_db, bs) !=
+ build_db.schema_version (bs))
+ {
+ cerr << "error: build database schema differs from the current one"
+ << endl << " info: use brep-migrate to migrate the database" << endl;
+ return 1;
+ }
+
+ const string ps ("package");
+ if (schema_catalog::current_version (package_db, ps) !=
+ package_db.schema_version (ps))
+ {
+ cerr << "error: package database schema differs from the current one"
+ << endl << " info: use brep-migrate to migrate the database" << endl;
+ return 1;
+ }
+
+ // Prepare the build prepared query.
+ //
+ // Query package builds in chunks in order not to hold locks for too long.
+ // Sort the result by package version to minimize number of queries to the
+ // package database.
+ //
+ using bld_query = query<build>;
+ using prep_bld_query = prepared_query<build>;
+
+ size_t offset (0);
+ bld_query bq ("ORDER BY" + bld_query::id.package.name +
+ order_by_version_desc (bld_query::id.package.version, false) +
+ "OFFSET" + bld_query::_ref (offset) + "LIMIT 100");
+
+ connection_ptr bld_conn (build_db.connection ());
+
+ prep_bld_query bld_prep_query (
+ bld_conn->prepare_query<build> ("build-query", bq));
+
+ // Prepare the package version query.
+ //
+ // Query package versions every time the new package name is encountered
+ // during iterating over the package builds. Such a query will be made once
+ // per package name due to the builds query sorting criteria (see above).
+ //
+ using pkg_query = query<package_version>;
+ using prep_pkg_query = prepared_query<package_version>;
+
+ string package_name;
+ set<version> package_versions;
+
+ pkg_query pq (pkg_query::package::id.name == pkg_query::_ref (package_name));
+
+ connection_ptr pkg_conn (package_db.connection ());
+
+ prep_pkg_query pkg_prep_query (
+ pkg_conn->prepare_query<package_version> ("package-version-query", pq));
+
+ while (true)
+ {
+ // Start the build database transaction.
+ //
+ transaction bt (bld_conn->begin ());
+
+ // Query builds.
+ //
+ auto builds (bld_prep_query.execute ());
+
+ if (!builds.empty ())
+ {
+ // Start the package database transaction.
+ //
+ transaction pt (pkg_conn->begin (), false);
+
+ for (const auto& b: builds)
+ {
+ auto i (timeouts.find (b.toolchain_name));
+
+ timestamp et (i != timeouts.end ()
+ ? i->second
+ : default_timeout);
+
+ bool cleanup (
+ // Check that the build is not stale.
+ //
+ b.timestamp <= et ||
+
+ // Check that the build configuration is still present.
+ //
+ // Note that we unable to detect configuration changes and rely on
+ // periodic rebuilds to take care of that.
+ //
+ configs.find (b.configuration) == configs.end ());
+
+ // Check that the build package still exists.
+ //
+ if (!cleanup)
+ {
+ if (package_name != b.package_name)
+ {
+ // Switch to the package database transaction.
+ //
+ transaction::current (pt);
+
+ package_name = b.package_name;
+ package_versions.clear ();
+
+ for (auto& v: pkg_prep_query.execute ())
+ package_versions.emplace (move (v.version));
+
+ // Switch back to the build database transaction.
+ //
+ transaction::current (bt);
+ }
+
+ cleanup = package_versions.find (b.package_version) ==
+ package_versions.end ();
+ }
+
+ if (cleanup)
+ build_db.erase (b);
+ else
+ ++offset;
+ }
+
+ // Commit the package database transaction.
+ //
+ pt.commit ();
+ }
+
+ bt.commit ();
+
+ if (builds.empty ())
+ break;
+ }
+
+ return 0;
+}
+catch (const database_locked&)
+{
+ cerr << "brep-clean or brep-migrate is running" << endl;
+ return 2;
+}
+catch (const recoverable& e)
+{
+ cerr << "recoverable database error: " << e << endl;
+ return 3;
+}
+catch (const cli::exception& e)
+{
+ cerr << "error: " << e << endl << help_info << endl;
+ return 1;
+}
+catch (const failed&)
+{
+ return 1; // Diagnostics has already been issued.
+}
+// Fully qualified to avoid ambiguity with odb exception.
+//
+catch (const std::exception& e)
+{
+ cerr << "error: " << e << endl;
+ return 1;
+}