aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/bash/buildfile65
-rw-r--r--libbuild2/bash/export.hxx34
-rw-r--r--libbuild2/bash/init.cxx104
-rw-r--r--libbuild2/bash/init.hxx28
-rw-r--r--libbuild2/bash/rule.cxx442
-rw-r--r--libbuild2/bash/rule.hxx91
-rw-r--r--libbuild2/bash/target.cxx30
-rw-r--r--libbuild2/bash/target.hxx34
-rw-r--r--libbuild2/bash/utility.hxx27
-rw-r--r--libbuild2/buildfile2
10 files changed, 856 insertions, 1 deletions
diff --git a/libbuild2/bash/buildfile b/libbuild2/bash/buildfile
new file mode 100644
index 0000000..e1a9f53
--- /dev/null
+++ b/libbuild2/bash/buildfile
@@ -0,0 +1,65 @@
+# file : libbuild2/bash/buildfile
+# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import int_libs = libbutl%lib{butl}
+
+include ../
+int_libs += ../lib{build2}
+
+include ../in/
+int_libs += ../in/lib{build2-in}
+
+./: lib{build2-bash}: libul{build2-bash}: {hxx ixx txx cxx}{** -**.test...} \
+ $int_libs
+
+# Unit tests.
+#
+exe{*.test}:
+{
+ test = true
+ install = false
+}
+
+for t: cxx{**.test...}
+{
+ d = $directory($t)
+ n = $name($t)...
+
+ ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n}
+ $d/exe{$n}: libul{build2-bash}: bin.whole = false
+}
+
+# Build options.
+#
+obja{*}: cxx.poptions += -DLIBBUILD2_BASH_STATIC_BUILD
+objs{*}: cxx.poptions += -DLIBBUILD2_BASH_SHARED_BUILD
+
+# Export options.
+#
+lib{build2-bash}:
+{
+ cxx.export.poptions = "-I$out_root" "-I$src_root"
+ cxx.export.libs = $int_libs
+}
+
+liba{build2-bash}: cxx.export.poptions += -DLIBBUILD2_BASH_STATIC
+libs{build2-bash}: cxx.export.poptions += -DLIBBUILD2_BASH_SHARED
+
+# For pre-releases use the complete version to make sure they cannot be used
+# in place of another pre-release or the final version. See the version module
+# for details on the version.* variable values.
+#
+if $version.pre_release
+ lib{build2-bash}: bin.lib.version = @"-$version.project_id"
+else
+ lib{build2-bash}: bin.lib.version = @"-$version.major.$version.minor"
+
+# Install into the libbuild2/bash/ subdirectory of, say, /usr/include/
+# recreating subdirectories.
+#
+{hxx ixx txx}{*}:
+{
+ install = include/libbuild2/bash/
+ install.subdirs = true
+}
diff --git a/libbuild2/bash/export.hxx b/libbuild2/bash/export.hxx
new file mode 100644
index 0000000..d87e677
--- /dev/null
+++ b/libbuild2/bash/export.hxx
@@ -0,0 +1,34 @@
+#pragma once
+
+// Normally we don't export class templates (but do complete specializations),
+// inline functions, and classes with only inline member functions. Exporting
+// classes that inherit from non-exported/imported bases (e.g., std::string)
+// will end up badly. The only known workarounds are to not inherit or to not
+// export. Also, MinGW GCC doesn't like seeing non-exported functions being
+// used before their inline definition. The workaround is to reorder code. In
+// the end it's all trial and error.
+
+#if defined(LIBBUILD2_BASH_STATIC) // Using static.
+# define LIBBUILD2_BASH_SYMEXPORT
+#elif defined(LIBBUILD2_BASH_STATIC_BUILD) // Building static.
+# define LIBBUILD2_BASH_SYMEXPORT
+#elif defined(LIBBUILD2_BASH_SHARED) // Using shared.
+# ifdef _WIN32
+# define LIBBUILD2_BASH_SYMEXPORT __declspec(dllimport)
+# else
+# define LIBBUILD2_BASH_SYMEXPORT
+# endif
+#elif defined(LIBBUILD2_BASH_SHARED_BUILD) // Building shared.
+# ifdef _WIN32
+# define LIBBUILD2_BASH_SYMEXPORT __declspec(dllexport)
+# else
+# define LIBBUILD2_BASH_SYMEXPORT
+# endif
+#else
+// If none of the above macros are defined, then we assume we are being used
+// by some third-party build system that cannot/doesn't signal the library
+// type. Note that this fallback works for both static and shared but in case
+// of shared will be sub-optimal compared to having dllimport.
+//
+# define LIBBUILD2_BASH_SYMEXPORT // Using static or shared.
+#endif
diff --git a/libbuild2/bash/init.cxx b/libbuild2/bash/init.cxx
new file mode 100644
index 0000000..17c9ddd
--- /dev/null
+++ b/libbuild2/bash/init.cxx
@@ -0,0 +1,104 @@
+// file : libbuild2/bash/init.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/bash/init.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/context.hxx>
+#include <libbuild2/variable.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/install/utility.hxx>
+
+#include <libbuild2/bash/rule.hxx>
+#include <libbuild2/bash/target.hxx>
+#include <libbuild2/bash/utility.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace bash
+ {
+ static const in_rule in_rule_;
+ static const install_rule install_rule_ (in_rule_);
+
+ bool
+ init (scope& rs,
+ scope& bs,
+ const location& l,
+ unique_ptr<module_base>&,
+ bool,
+ bool,
+ const variable_map&)
+ {
+ tracer trace ("bash::init");
+ l5 ([&]{trace << "for " << bs;});
+
+ // Load in.base (in.* variables, in{} target type).
+ //
+ if (!cast_false<bool> (rs["in.base.loaded"]))
+ load_module (rs, rs, "in.base", l);
+
+ bool install_loaded (cast_false<bool> (rs["install.loaded"]));
+
+ // Register target types and configure default installability.
+ //
+ bs.target_types.insert<bash> ();
+
+ if (install_loaded)
+ {
+ using namespace install;
+
+ // Install into bin/<project>/ by default stripping the .bash
+ // extension from <project> if present.
+ //
+ const project_name& p (cast<project_name> (rs.vars[var_project]));
+
+ install_path<bash> (bs, dir_path ("bin") /= project_base (p));
+ install_mode<bash> (bs, "644");
+ }
+
+ // Register rules.
+ //
+ {
+ auto& r (bs.rules);
+
+ r.insert<exe> (perform_update_id, "bash.in", in_rule_);
+ r.insert<exe> (perform_clean_id, "bash.in", in_rule_);
+ r.insert<exe> (configure_update_id, "bash.in", in_rule_);
+
+ r.insert<bash> (perform_update_id, "bash.in", in_rule_);
+ r.insert<bash> (perform_clean_id, "bash.in", in_rule_);
+ r.insert<bash> (configure_update_id, "bash.in", in_rule_);
+
+ if (install_loaded)
+ {
+ r.insert<exe> (perform_install_id, "bash.install", install_rule_);
+ r.insert<exe> (perform_uninstall_id, "bash.uninstall", install_rule_);
+
+ r.insert<bash> (perform_install_id, "bash.install", install_rule_);
+ r.insert<bash> (perform_uninstall_id, "bash.uninstall", install_rule_);
+ }
+ }
+
+ return true;
+ }
+
+ static const module_functions mod_functions[] =
+ {
+ // NOTE: don't forget to also update the documentation in init.hxx if
+ // changing anything here.
+
+ {"bash", nullptr, init},
+ {nullptr, nullptr, nullptr}
+ };
+
+ const module_functions*
+ build2_bash_load ()
+ {
+ return mod_functions;
+ }
+ }
+}
diff --git a/libbuild2/bash/init.hxx b/libbuild2/bash/init.hxx
new file mode 100644
index 0000000..4d05f2d
--- /dev/null
+++ b/libbuild2/bash/init.hxx
@@ -0,0 +1,28 @@
+// file : libbuild2/bash/init.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_BASH_INIT_HXX
+#define LIBBUILD2_BASH_INIT_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/module.hxx>
+
+#include <libbuild2/bash/export.hxx>
+
+namespace build2
+{
+ namespace bash
+ {
+ // Module `bash` does not require bootstrapping.
+ //
+ // No submodules.
+ //
+ extern "C" LIBBUILD2_BASH_SYMEXPORT const module_functions*
+ build2_bash_load ();
+ }
+}
+
+#endif // LIBBUILD2_BASH_INIT_HXX
diff --git a/libbuild2/bash/rule.cxx b/libbuild2/bash/rule.cxx
new file mode 100644
index 0000000..d9bf857
--- /dev/null
+++ b/libbuild2/bash/rule.cxx
@@ -0,0 +1,442 @@
+// file : libbuild2/bash/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/bash/rule.hxx>
+
+#include <cstring> // strlen(), strchr()
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/algorithm.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/in/target.hxx>
+
+#include <libbuild2/bash/target.hxx>
+#include <libbuild2/bash/utility.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace bash
+ {
+ using in::in;
+
+ struct match_data
+ {
+ // The "for install" condition is signalled to us by install_rule when
+ // it is matched for the update operation. It also verifies that if we
+ // have already been executed, then it was for install.
+ //
+ // See cc::link_rule for a discussion of some subtleties in this logic.
+ //
+ optional<bool> for_install;
+ };
+
+ static_assert (sizeof (match_data) <= target::data_size,
+ "insufficient space");
+
+ // in_rule
+ //
+ bool in_rule::
+ match (action a, target& t, const string&) const
+ {
+ tracer trace ("bash::in_rule::match");
+
+ // Note that for bash{} we match even if the target does not depend on
+ // any modules (while it could have been handled by the in module, that
+ // would require loading it).
+ //
+ bool fi (false); // Found in.
+ bool fm (t.is_a<bash> ()); // Found module.
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
+ fi = fi || p.is_a<in> ();
+ fm = fm || p.is_a<bash> ();
+ }
+
+ if (!fi)
+ l4 ([&]{trace << "no in file prerequisite for target " << t;});
+
+ if (!fm)
+ l4 ([&]{trace << "no bash module prerequisite for target " << t;});
+
+ return (fi && fm);
+ }
+
+ recipe in_rule::
+ apply (action a, target& t) const
+ {
+ // Note that for-install is signalled by install_rule and therefore
+ // can only be relied upon during execute.
+ //
+ t.data (match_data ());
+
+ return rule::apply (a, t);
+ }
+
+ target_state in_rule::
+ perform_update (action a, const target& t) const
+ {
+ // Unless the outer install rule signalled that this is update for
+ // install, signal back that we've performed plain update.
+ //
+ match_data& md (t.data<match_data> ());
+
+ if (!md.for_install)
+ md.for_install = false;
+
+ return rule::perform_update (a, t);
+ }
+
+ prerequisite_target in_rule::
+ search (action a,
+ const target& t,
+ const prerequisite_member& pm,
+ include_type i) const
+ {
+ tracer trace ("bash::in_rule::search");
+
+ // Handle import of installed bash{} modules.
+ //
+ if (i == include_type::normal && pm.proj () && pm.is_a<bash> ())
+ {
+ // We only need this during update.
+ //
+ if (a != perform_update_id)
+ return nullptr;
+
+ const prerequisite& p (pm.prerequisite);
+
+ // Form the import path.
+ //
+ // Note that unless specified, we use the standard .bash extension
+ // instead of going through the bash{} target type since this path is
+ // not in our project (and thus no project-specific customization
+ // apply).
+ //
+ string ext (p.ext ? *p.ext : "bash");
+ path ip (dir_path (project_base (*p.proj)) / p.dir / p.name);
+
+ if (!ext.empty ())
+ {
+ ip += '.';
+ ip += ext;
+ }
+
+ // Search in PATH, similar to butl::path_search().
+ //
+ if (optional<string> s = getenv ("PATH"))
+ {
+ for (const char* b (s->c_str ()), *e;
+ b != nullptr;
+ b = (e != nullptr ? e + 1 : e))
+ {
+ e = strchr (b, path::traits_type::path_separator);
+
+ // Empty path (i.e., a double colon or a colon at the beginning or
+ // end of PATH) means search in the current dirrectory. We aren't
+ // going to do that. Also silently skip invalid paths, stat()
+ // errors, etc.
+ //
+ if (size_t n = (e != nullptr ? e - b : strlen (b)))
+ {
+ try
+ {
+ path ap (b, n);
+ ap /= ip;
+ ap.normalize ();
+
+ timestamp mt (file_mtime (ap));
+
+ if (mt != timestamp_nonexistent)
+ {
+ auto rp (targets.insert_locked (bash::static_type,
+ ap.directory (),
+ dir_path () /* out */,
+ p.name,
+ ext,
+ true /* implied */,
+ trace));
+
+ bash& pt (rp.first.as<bash> ());
+
+ // Only set mtime/path on first insertion.
+ //
+ if (rp.second.owns_lock ())
+ {
+ pt.mtime (mt);
+ pt.path (move (ap));
+ }
+
+ // Save the length of the import path in auxuliary data. We
+ // use it in substitute_import() to infer the installation
+ // directory.
+ //
+ return prerequisite_target (&pt, i, ip.size ());
+ }
+ }
+ catch (const invalid_path&) {}
+ catch (const system_error&) {}
+ }
+ }
+ }
+
+ // Let standard search() handle it.
+ }
+
+ return rule::search (a, t, pm, i);
+ }
+
+ optional<string> in_rule::
+ substitute (const location& l,
+ action a,
+ const target& t,
+ const string& n,
+ bool strict) const
+ {
+ return n.compare (0, 6, "import") == 0 && (n[6] == ' ' || n[6] == '\t')
+ ? substitute_import (l, a, t, trim (string (n, 7)))
+ : rule::substitute (l, a, t, n, strict);
+ }
+
+ string in_rule::
+ substitute_import (const location& l,
+ action a,
+ const target& t,
+ const string& n) const
+ {
+ // Derive (relative) import path from the import name.
+ //
+ path ip;
+
+ try
+ {
+ ip = path (n);
+
+ if (ip.empty () || ip.absolute ())
+ throw invalid_path (n);
+
+ if (ip.extension_cstring () == nullptr)
+ ip += ".bash";
+
+ ip.normalize ();
+ }
+ catch (const invalid_path&)
+ {
+ fail (l) << "invalid import path '" << n << "'";
+ }
+
+ // Look for a matching prerequisite.
+ //
+ const path* ap (nullptr);
+ for (const prerequisite_target& pt: t.prerequisite_targets[a])
+ {
+ if (pt.target == nullptr || pt.adhoc)
+ continue;
+
+ if (const bash* b = pt.target->is_a<bash> ())
+ {
+ const path& pp (b->path ());
+ assert (!pp.empty ()); // Should have been assigned by update.
+
+ // The simple "tail match" can be ambigous. Consider, for example,
+ // the foo/bar.bash import path and /.../foo/bar.bash as well as
+ // /.../x/foo/bar.bash prerequisites: they would both match.
+ //
+ // So the rule is the match must be from the project root directory
+ // or from the installation directory for the import-installed
+ // prerequisites.
+ //
+ // But we still do a simple match first since it can quickly weed
+ // out candidates that cannot possibly match.
+ //
+ if (!pp.sup (ip))
+ continue;
+
+ // See if this is import-installed target (refer to search() for
+ // details).
+ //
+ if (size_t n = pt.data)
+ {
+ // Both are normalized so we can compare the "tails".
+ //
+ const string& ps (pp.string ());
+ const string& is (ip.string ());
+
+ if (path::traits_type::compare (
+ ps.c_str () + ps.size () - n, n,
+ is.c_str (), is.size ()) == 0)
+ {
+ ap = &pp;
+ break;
+ }
+ else
+ continue;
+ }
+
+ if (const scope* rs = scopes.find (b->dir).root_scope ())
+ {
+ const dir_path& d (pp.sub (rs->src_path ())
+ ? rs->src_path ()
+ : rs->out_path ());
+
+ if (pp.leaf (d) == ip)
+ {
+ ap = &pp;
+ break;
+ }
+ else
+ continue;
+ }
+
+ fail (l) << "target " << *b << " is out of project nor imported";
+ }
+ }
+
+ if (ap == nullptr)
+ fail (l) << "unable to resolve import path " << ip;
+
+ match_data& md (t.data<match_data> ());
+ assert (md.for_install);
+
+ if (*md.for_install)
+ {
+ // For the installed case we assume the script and all its modules are
+ // installed into the same location (i.e., the same bin/ directory)
+ // and so we use the path relative to the script.
+ //
+ // BTW, the semantics of the source builtin in bash is to search in
+ // PATH if it's a simple path (that is, does not contain directory
+ // components) and then in the current working directory.
+ //
+ // So we have to determine the scripts's directory ourselves for which
+ // we use the BASH_SOURCE array. Without going into the gory details,
+ // the last element in this array is the script's path regardless of
+ // whether we are in the script or (sourced) module (but it turned out
+ // not to be what we need; see below).
+ //
+ // We also want to get the script's "real" directory even if it was
+ // itself symlinked somewhere else. And this is where things get
+ // hairy: we could use either realpath or readlink -f but neither is
+ // available on Mac OS (there is readlink but it doesn't support the
+ // -f option).
+ //
+ // One can get GNU readlink from Homebrew but it will be called
+ // greadlink. Note also that for any serious development one will
+ // probably be also getting newer bash from Homebrew since the system
+ // one is stuck in the GPLv2 version 3.2.X era. So a bit of a mess.
+ //
+ // For now let's use readlink -f and see how it goes. If someone wants
+ // to use/support their scripts on Mac OS, they have several options:
+ //
+ // 1. Install greadlink (coreutils) and symlink it as readlink.
+ //
+ // 2. Add the readlink function to their script that does nothing;
+ // symlinking scripts won't be supported but the rest should work
+ // fine.
+ //
+ // 3. Add the readlink function to their script that calls greadlink.
+ //
+ // 4. Add the readlink function to their script that implements the
+ // -f mode (or at least the part of it that we need). See the bash
+ // module tests for some examples.
+ //
+ // In the future we could automatically inject an implementation along
+ // the (4) lines at the beginning of the script.
+ //
+ // Note also that we really, really want to keep the substitution a
+ // one-liner since the import can be in an (indented) if-block, etc.,
+ // and we still want the resulting scripts to be human-readable.
+ //
+ if (t.is_a<exe> ())
+ {
+ return
+ "source \"$(dirname"
+ " \"$(readlink -f"
+ " \"${BASH_SOURCE[0]}\")\")/"
+ + ip.string () + "\"";
+ }
+ else
+ {
+ // Things turned out to be trickier for the installed modules: we
+ // cannot juts use the script's path since it itself might not be
+ // installed (import installed). So we have to use the importer's
+ // path and calculate its "offset" to the installation directory.
+ //
+ dir_path d (t.dir.leaf (t.root_scope ().out_path ()));
+
+ string o;
+ for (auto i (d.begin ()), e (d.end ()); i != e; ++i)
+ o += "../";
+
+ // Here we don't use readlink since we assume nobody will symlink
+ // the modules (or they will all be symlinked together).
+ //
+ return
+ "source \"$(dirname"
+ " \"${BASH_SOURCE[0]}\")/"
+ + o + ip.string () + "\"";
+ }
+ }
+ else
+ return "source " + ap->string ();
+ }
+
+ // install_rule
+ //
+ bool install_rule::
+ match (action a, target& t, const string& hint) const
+ {
+ // We only want to handle installation if we are also the ones building
+ // this target. So first run in's match().
+ //
+ return in_.match (a, t, hint) && file_rule::match (a, t, "");
+ }
+
+ recipe install_rule::
+ apply (action a, target& t) const
+ {
+ recipe r (file_rule::apply (a, t));
+
+ if (a.operation () == update_id)
+ {
+ // Signal to the in rule that this is update for install. And if the
+ // update has already been executed, verify it was done for install.
+ //
+ auto& md (t.data<match_data> ());
+
+ if (md.for_install)
+ {
+ if (!*md.for_install)
+ fail << "target " << t << " already updated but not for install";
+ }
+ else
+ md.for_install = true;
+ }
+
+ return r;
+ }
+
+ const target* install_rule::
+ filter (action a, const target& t, const prerequisite& p) const
+ {
+ // If this is a module prerequisite, install it as long as it is in the
+ // same amalgamation as we are.
+ //
+ if (p.is_a<bash> ())
+ {
+ const target& pt (search (t, p));
+ return pt.in (t.weak_scope ()) ? &pt : nullptr;
+ }
+ else
+ return file_rule::filter (a, t, p);
+ }
+ }
+}
diff --git a/libbuild2/bash/rule.hxx b/libbuild2/bash/rule.hxx
new file mode 100644
index 0000000..665a6c2
--- /dev/null
+++ b/libbuild2/bash/rule.hxx
@@ -0,0 +1,91 @@
+// file : libbuild2/bash/rule.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_BASH_RULE_HXX
+#define LIBBUILD2_BASH_RULE_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/install/rule.hxx>
+
+#include <libbuild2/in/rule.hxx>
+
+#include <libbuild2/bash/export.hxx>
+
+namespace build2
+{
+ namespace bash
+ {
+ // Preprocess a bash script (exe{}) or module (bash{}) .in file that
+ // imports one or more bash modules.
+ //
+ // Note that the default substitution symbol is '@' and the mode is lax
+ // (think bash arrays). The idea is that '@' is normally used in ways that
+ // are highly unlikely to be misinterpreted as substitutions. The user,
+ // however, is still able to override both of these choices with the
+ // corresponding in.* variables (e.g., to use '`' and strict mode).
+ //
+ class LIBBUILD2_BASH_SYMEXPORT in_rule: public in::rule
+ {
+ public:
+ in_rule (): rule ("bash.in 1", "bash.in", '@', false /* strict */) {}
+
+ virtual bool
+ match (action, target&, const string&) const override;
+
+ virtual recipe
+ apply (action, target&) const override;
+
+ virtual target_state
+ perform_update (action, const target&) const override;
+
+ virtual prerequisite_target
+ search (action,
+ const target&,
+ const prerequisite_member&,
+ include_type) const override;
+
+ virtual optional<string>
+ substitute (const location&,
+ action a,
+ const target&,
+ const string&,
+ bool) const override;
+
+ string
+ substitute_import (const location&,
+ action a,
+ const target&,
+ const string&) const;
+ };
+
+ // Installation rule for bash scripts (exe{}) and modules (bash{}). Here
+ // we do:
+ //
+ // 1. Signal to in_rule that this is update for install.
+ //
+ // 2. Custom filtering of prerequisites.
+ //
+ class LIBBUILD2_BASH_SYMEXPORT install_rule: public install::file_rule
+ {
+ public:
+ install_rule (const in_rule& in): in_ (in) {}
+
+ virtual bool
+ match (action, target&, const string&) const override;
+
+ virtual recipe
+ apply (action, target&) const override;
+
+ virtual const target*
+ filter (action, const target&, const prerequisite&) const override;
+
+ protected:
+ const in_rule& in_;
+ };
+ }
+}
+
+#endif // LIBBUILD2_BASH_RULE_HXX
diff --git a/libbuild2/bash/target.cxx b/libbuild2/bash/target.cxx
new file mode 100644
index 0000000..7313316
--- /dev/null
+++ b/libbuild2/bash/target.cxx
@@ -0,0 +1,30 @@
+// file : libbuild2/bash/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/bash/target.hxx>
+
+#include <libbuild2/context.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace bash
+ {
+ extern const char bash_ext_def[] = "bash";
+
+ const target_type bash::static_type
+ {
+ "bash",
+ &file::static_type,
+ &target_factory<bash>,
+ nullptr, /* fixed_extension */
+ &target_extension_var<var_extension, bash_ext_def>,
+ &target_pattern_var<var_extension, bash_ext_def>,
+ nullptr,
+ &file_search,
+ false
+ };
+ }
+}
diff --git a/libbuild2/bash/target.hxx b/libbuild2/bash/target.hxx
new file mode 100644
index 0000000..af8b32c
--- /dev/null
+++ b/libbuild2/bash/target.hxx
@@ -0,0 +1,34 @@
+// file : libbuild2/bash/target.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_BASH_TARGET_HXX
+#define LIBBUILD2_BASH_TARGET_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/target.hxx>
+
+#include <libbuild2/bash/export.hxx>
+
+namespace build2
+{
+ namespace bash
+ {
+ // Bash module file to be sourced by a script. The default/standard
+ // extension is .bash.
+ //
+ class LIBBUILD2_BASH_SYMEXPORT bash: public file
+ {
+ public:
+ using file::file;
+
+ public:
+ static const target_type static_type;
+ virtual const target_type& dynamic_type () const {return static_type;}
+ };
+ }
+}
+
+#endif // LIBBUILD2_BASH_TARGET_HXX
diff --git a/libbuild2/bash/utility.hxx b/libbuild2/bash/utility.hxx
new file mode 100644
index 0000000..1f981c2
--- /dev/null
+++ b/libbuild2/bash/utility.hxx
@@ -0,0 +1,27 @@
+// file : libbuild2/bash/utility.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_BASH_UTILITY_HXX
+#define LIBBUILD2_BASH_UTILITY_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+namespace build2
+{
+ namespace bash
+ {
+ // Strip the .bash extension from the project name.
+ //
+ // Note that the result may not be a valid project name.
+ //
+ inline string
+ project_base (const project_name& pn)
+ {
+ return pn.base ("bash");
+ }
+ }
+}
+
+#endif // LIBBUILD2_BASH_UTILITY_HXX
diff --git a/libbuild2/buildfile b/libbuild2/buildfile
index ab95098..377ea30 100644
--- a/libbuild2/buildfile
+++ b/libbuild2/buildfile
@@ -2,7 +2,7 @@
# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
-./: lib{build2} version/ in/
+./: lib{build2} bash/ in/ version/
import int_libs = libbutl%lib{butl}