aboutsummaryrefslogtreecommitdiff
path: root/build/dist/operation.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'build/dist/operation.cxx')
-rw-r--r--build/dist/operation.cxx423
1 files changed, 423 insertions, 0 deletions
diff --git a/build/dist/operation.cxx b/build/dist/operation.cxx
new file mode 100644
index 0000000..3c6ec98
--- /dev/null
+++ b/build/dist/operation.cxx
@@ -0,0 +1,423 @@
+// file : build/dist/operation.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build/dist/operation>
+
+#include <cassert>
+
+#include <butl/process>
+#include <butl/filesystem>
+
+#include <build/file>
+#include <build/dump>
+#include <build/scope>
+#include <build/target>
+#include <build/context>
+#include <build/algorithm>
+#include <build/diagnostics>
+
+using namespace std;
+using namespace butl;
+
+namespace build
+{
+ namespace dist
+ {
+ static void
+ dist_meta_operation_pre ()
+ {
+ // Reset the dependency state so that we don't end up with stray
+ // files from previous batches.
+ //
+ // @@ This is called too late, after we have bootstrapped the
+ // project.
+ //
+ //reset ();
+ }
+
+ static operation_id
+ dist_operation_pre (operation_id o)
+ {
+ if (o != default_id)
+ fail << "explicit operation specified for dist meta-operation";
+
+ return o;
+ }
+
+ static void
+ dist_match (action, action_targets&)
+ {
+ // Don't match anything -- see execute ().
+ }
+
+ // install -d <dir>
+ //
+ static void
+ install (const string& cmd, const dir_path&);
+
+ // install <file> <dir>
+ //
+ static void
+ install (const string& cmd, file&, const dir_path&);
+
+ // cd <root> && tar|zip ... <pkg>.<ext> <pkg>
+ //
+ static void
+ archive (const dir_path& root, const string& pkg, const string& ext);
+
+ static void
+ dist_execute (action, const action_targets& ts, bool)
+ {
+ tracer trace ("dist_execute");
+
+ // For now we assume all the targets are from the same project.
+ //
+ target& t (*static_cast<target*> (ts[0]));
+ scope* rs (t.base_scope ().root_scope ());
+
+ if (rs == nullptr)
+ fail << "out of project target " << t;
+
+ const dir_path& out_root (rs->path ());
+ const dir_path& src_root (rs->src_path ());
+
+ if (out_root == src_root)
+ fail << "in-tree distribution of target " << t <<
+ info << "distribution requires out-of-tree build";
+
+ // Make sure we have the necessary configuration before
+ // we get down to business.
+ //
+ auto l (rs->vars["dist.root"]);
+
+ if (!l || l->empty ())
+ fail << "unknown root distribution directory" <<
+ info << "did you forget to specify config.dist.root?";
+
+ const dir_path& dist_root (as<dir_path> (*l));
+
+ if (!dir_exists (dist_root))
+ fail << "root distribution directory " << dist_root
+ << " does not exist";
+
+ l = rs->vars["dist.package"];
+
+ if (!l || l->empty ())
+ fail << "unknown distribution package name" <<
+ info << "did you forget to set dist.package?";
+
+ const string& dist_package (as<string> (*l));
+ const string& dist_cmd (as<string> (*rs->vars["dist.cmd"]));
+
+ // Get the list of operations supported by this project. Skip
+ // default_id.
+ //
+ for (operations::size_type id (default_id + 1);
+ id < rs->operations.size ();
+ ++id)
+ {
+ const operation_info* oi (rs->operations[id]);
+ if (oi == nullptr)
+ continue;
+
+ // Note that we are not calling operation_pre/post() callbacks
+ // here since the meta operation is dist and we know what we
+ // are doing.
+ //
+ current_inner_oif = oi;
+ current_outer_oif = nullptr;
+ current_mode = oi->mode;
+ dependency_count = 0;
+
+ action a (dist_id, id);
+
+ if (verb >= 5)
+ dump (a);
+
+ for (void* v: ts)
+ {
+ target& t (*static_cast<target*> (v));
+
+ if (rs != t.base_scope ().root_scope ())
+ fail << "out of project target " << t;
+
+ level4 ([&]{trace << diag_doing (a, t);});
+
+ match (a, t);
+ }
+
+ if (verb >= 5)
+ dump (a);
+ }
+
+ // Add buildfiles that are not normally loaded as part of the
+ // project, for example, the export stub. They will still be
+ // ignored on the next step if the user explicitly marked them
+ // nodist.
+ //
+ auto add_adhoc = [&src_root, &trace] (const char* f)
+ {
+ path p (src_root / path (f));
+ if (file_exists (p))
+ {
+ const char* e (p.extension ());
+ targets.insert<buildfile> (
+ p.directory (),
+ p.leaf ().base ().string (),
+ &extension_pool.find (e == nullptr ? "" : e), // Specified.
+ trace);
+ }
+ };
+
+ add_adhoc ("build/export.build");
+
+ // Collect the files. We want to take the snapshot of targets
+ // since updating some of them may result in more targets being
+ // entered.
+ //
+ action_targets files;
+
+ for (const auto& pt: targets)
+ {
+ file* ft (pt->is_a<file> ());
+
+ if (ft == nullptr) // Not a file.
+ continue;
+
+ if (ft->dir.sub (src_root))
+ {
+ // Include unless explicitly excluded.
+ //
+ files.push_back (ft);
+ continue;
+ }
+
+ if (ft->dir.sub (out_root))
+ {
+ // Exclude unless explicitly included.
+ //
+ //files.push_back (*ft);
+ continue;
+ }
+ }
+
+ // Make sure what we need to distribute is up to date.
+ //
+ {
+ if (perform.meta_operation_pre != nullptr)
+ perform.meta_operation_pre ();
+
+ current_mif = &perform;
+
+ if (perform.operation_pre != nullptr)
+ perform.operation_pre (update_id);
+
+ current_inner_oif = &update;
+ current_outer_oif = nullptr;
+ current_mode = update.mode;
+ dependency_count = 0;
+
+ action a (perform_id, update_id);
+
+ perform.match (a, files);
+ perform.execute (a, files, true); // Run quiet.
+
+ if (perform.operation_post != nullptr)
+ perform.operation_post (update_id);
+
+ if (perform.meta_operation_post != nullptr)
+ perform.meta_operation_post ();
+ }
+
+ dir_path td (dist_root / dir_path (dist_package));
+
+ // Clean up the target directory.
+ //
+ // @@ Not for incremental dist?
+ //
+ if (build::rmdir_r (td) == rmdir_status::not_empty)
+ fail << "unable to clean target directory " << td;
+
+ install (dist_cmd, td);
+
+ // Copy over all the files.
+ //
+ for (void* v: files)
+ {
+ file& t (*static_cast<file*> (v));
+
+ // Figure out where this file is inside the target directory.
+ //
+ dir_path d (td);
+ d /= t.dir.sub (src_root)
+ ? t.dir.leaf (src_root)
+ : t.dir.leaf (out_root);
+
+ if (!dir_exists (d))
+ install (dist_cmd, d);
+
+ install (dist_cmd, t, d);
+ }
+
+ // Archive if requested.
+ //
+ if (auto l = rs->vars["dist.archives"])
+ {
+ for (const string& e: as<strings> (*l))
+ archive (dist_root, dist_package, e);
+ }
+ }
+
+ // install -d <dir>
+ //
+ static void
+ install (const string& cmd, const dir_path& d)
+ {
+ path reld (relative (d));
+
+ cstrings args {cmd.c_str (), "-d"};
+
+ args.push_back ("-m");
+ args.push_back ("755");
+ args.push_back (reld.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb)
+ print_process (args);
+ else
+ text << "dist -d " << d;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ // install <file> <dir>
+ //
+ static void
+ install (const string& cmd, file& t, const dir_path& d)
+ {
+ path reld (relative (d));
+ path relf (relative (t.path ()));
+
+ cstrings args {cmd.c_str ()};
+
+ // Preserve timestamps. This could becomes important if, for
+ // example, we have pre-generated sources. Note that the
+ // install-sh script doesn't support this option, while both
+ // Linux and BSD install's do.
+ //
+ args.push_back ("-p");
+
+ // Assume the file is executable if the owner has execute
+ // permission, in which case we make it executable for
+ // everyone.
+ //
+ args.push_back ("-m");
+ args.push_back (
+ (path_permissions (t.path ()) & permissions::xu) == permissions::xu
+ ? "755"
+ : "644");
+
+ args.push_back (relf.string ().c_str ());
+ args.push_back (reld.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb)
+ print_process (args);
+ else
+ text << "dist " << t;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ static void
+ archive (const dir_path& root, const string& pkg, const string& e)
+ {
+ string a (pkg + '.' + e);
+
+ // Delete old archive for good measure.
+ //
+ path ap (root / path (a));
+ if (file_exists (ap))
+ rmfile (ap);
+
+ // Use zip for .zip archives. Everything else goes to tar in the
+ // auto-compress mode (-a).
+ //
+ cstrings args;
+ if (e == "zip")
+ args = {"zip", "-rq", a.c_str (), pkg.c_str (), nullptr};
+ else
+ args = {"tar", "-a", "-cf", a.c_str (), pkg.c_str (), nullptr};
+
+ if (verb)
+ print_process (args);
+ else
+ text << args[0] << " " << ap;
+
+ try
+ {
+ // Change child's working directory to dist_root.
+ //
+ process pr (root.string ().c_str (), args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+
+ meta_operation_info dist {
+ "dist",
+ "distribute",
+ "distributing",
+ "has nothing to distribute", // We cannot "be distributed".
+ &dist_meta_operation_pre,
+ &dist_operation_pre,
+ &load, // normal load
+ &search, // normal search
+ &dist_match,
+ &dist_execute,
+ nullptr, // operation post
+ nullptr // meta-operation post
+ };
+ }
+}