// file : libbuild2/build/script/script.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include <libbuild2/build/script/script.hxx> #include <libbutl/filesystem.mxx> #include <libbuild2/target.hxx> #include <libbuild2/script/timeout.hxx> #include <libbuild2/build/script/parser.hxx> using namespace std; namespace build2 { namespace build { namespace script { using build2::script::to_deadline; // environment // static const optional<string> wd_name ("current directory"); environment:: environment (action a, const target_type& t, bool temp, const optional<timestamp>& dl) : build2::script::environment ( t.ctx, cast<target_triplet> (t.ctx.global_scope["build.host"]), dir_name_view (&work, &wd_name), temp_dir.path, false /* temp_dir_keep */, redirect (redirect_type::none), redirect (redirect_type::merge, 2), redirect (redirect_type::pass)), target (t), vars (context, false /* global */), script_deadline (to_deadline (dl, false /* success */)) { // Set special variables. // { // $> // names ns; for (const target_type* m (&t); m != nullptr; m = m->adhoc_member) m->as_name (ns); assign (var_pool.insert (">")) = move (ns); } { // $< // // Note that at this stage (after execute_prerequisites()) ad hoc // prerequisites are no longer in prerequisite_targets which means // they won't end up in $< either. While at first thought ad hoc // prerequisites in ad hoc recipes don't seem to make much sense, // they could be handy to exclude certain preresquisites from $< // while still treating them as such. // names ns; for (const target_type* pt: t.prerequisite_targets[a]) { if (pt != nullptr) pt->as_name (ns); } assign (var_pool.insert ("<")) = move (ns); } // Set the $~ special variable. // if (temp) { create_temp_dir (); assign (var_pool.insert<dir_path> ("~")) = temp_dir.path; } } void environment:: create_temp_dir () { // Create the temporary directory for this run regardless of the // dry-run mode, since some commands still can be executed (see run() // for details). This is also the reason why we are not using the // build2 filesystem API that considers the dry-run mode. // // Note that the directory auto-removal is active. // dir_path& td (temp_dir.path); assert (td.empty ()); // Must be called once. try { td = dir_path::temp_path ("buildscript"); } catch (const system_error& e) { fail << "unable to obtain temporary directory for buildscript " << "execution" << e; } mkdir_status r; try { r = try_mkdir (td); } catch (const system_error& e) { fail << "unable to create temporary directory '" << td << "': " << e << endf; } // Note that the temporary directory can potentially stay after some // abnormally terminated script run. Clean it up and reuse if that's // the case. // if (r == mkdir_status::already_exists) try { butl::rmdir_r (td, false /* dir */); } catch (const system_error& e) { fail << "unable to cleanup temporary directory '" << td << "': " << e; } if (verb >= 3) text << "mkdir " << td; } void environment:: set_variable (string&& nm, names&& val, const string& attrs, const location& ll) { // Check if we are trying to modify any of the special variables. // if (parser::special_variable (nm)) fail (ll) << "attempt to set '" << nm << "' special variable"; // Set the variable value and attributes. // const variable& var (var_pool.insert (move (nm))); value& lhs (assign (var)); // If there are no attributes specified then the variable assignment // is straightforward. Otherwise we will use the build2 parser helper // function. // if (attrs.empty ()) lhs.assign (move (val), &var); else { // If there is an error in the attributes string, our diagnostics // will look like this: // // <attributes>:1:1 error: unknown value attribute x // buildfile:10:1 info: while parsing attributes '[x]' // // Note that the attributes parsing error is the only reason for a // failure. // auto df = make_diag_frame ( [attrs, &ll](const diag_record& dr) { dr << info (ll) << "while parsing attributes '" << attrs << "'"; }); parser p (context); p.apply_value_attributes (&var, lhs, value (move (val)), attrs, token_type::assign, path_name ("<attributes>")); } } lookup environment:: lookup (const variable& var) const { auto p (vars.lookup (var)); if (p.first != nullptr) return lookup_type (*p.first, p.second, vars); return lookup_in_buildfile (var.name); } lookup environment:: lookup (const string& name) const { // Every variable that is ever set in a script has been added during // variable line execution or introduced with the set builtin. Which // means that if one is not found in the environment pool then it can // only possibly be set in the buildfile. // const variable* pvar (var_pool.find (name)); return pvar != nullptr ? lookup (*pvar) : lookup_in_buildfile (name); } lookup environment:: lookup_in_buildfile (const string& n) const { // Switch to the corresponding buildfile variable. Note that we don't // want to insert a new variable into the pool (we might be running // in parallel). Plus, if there is no such variable, then we cannot // possibly find any value. // const variable* pvar (context.var_pool.find (n)); if (pvar == nullptr) return lookup_type (); return target[*pvar]; } value& environment:: append (const variable& var) { auto l (lookup (var)); if (l.defined () && l.belongs (*this)) // Existing var. return vars.modify (l); value& r (assign (var)); // NULL. if (l.defined ()) r = *l; // Copy value (and type) from the outer scope. return r; } void environment:: set_timeout (const string& t, bool success, const location& l) { fragment_deadline = to_deadline (parse_deadline (t, "buildscript timeout", l), success); } optional<deadline> environment:: effective_deadline () { return earlier (script_deadline, fragment_deadline); } } } }