diff options
Diffstat (limited to 'build/dist/operation.cxx')
-rw-r--r-- | build/dist/operation.cxx | 423 |
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 + }; + } +} |