aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bdep/new-types.hxx50
-rw-r--r--bdep/new.cli66
-rw-r--r--bdep/new.cxx74
-rw-r--r--tests/new.testscript102
4 files changed, 271 insertions, 21 deletions
diff --git a/bdep/new-types.hxx b/bdep/new-types.hxx
index 9d19c48..96aaa7c 100644
--- a/bdep/new-types.hxx
+++ b/bdep/new-types.hxx
@@ -46,20 +46,26 @@ namespace bdep
//
cmd_new_type_template (): type (exe) {}
- friend ostream&
- operator<< (ostream& os, const cmd_new_type_template& t)
+ const std::string
+ string () const
{
using type = cmd_new_type_template;
- switch (t)
+ switch (*this)
{
- case type::exe: return os << "executable";
- case type::lib: return os << "library";
- case type::bare: return os << "bare";
- case type::empty: return os << "empty";
+ case type::exe: return "executable";
+ case type::lib: return "library";
+ case type::bare: return "bare";
+ case type::empty: return "empty";
}
- return os;
+ return string (); // Should never reach.
+ }
+
+ friend ostream&
+ operator<< (ostream& os, const cmd_new_type_template& t)
+ {
+ return os << t.string ();
}
};
@@ -84,6 +90,20 @@ namespace bdep
// Default is C++ with no options.
//
cmd_new_lang_template (): lang (cxx) {}
+
+ const std::string
+ string () const
+ {
+ using lang = cmd_new_lang_template;
+
+ switch (*this)
+ {
+ case lang::c: return "c";
+ case lang::cxx: return "c++";
+ }
+
+ return string (); // Should never reach.
+ }
};
using cmd_new_lang = cmd_new_lang_template<>;
@@ -107,6 +127,20 @@ namespace bdep
// Default is git with no options.
//
cmd_new_vcs_template (): vcs (git) {}
+
+ const std::string
+ string () const
+ {
+ using vcs = cmd_new_vcs_template;
+
+ switch (*this)
+ {
+ case vcs::git: return "git";
+ case vcs::none: return "none";
+ }
+
+ return string (); // Should never reach.
+ }
};
using cmd_new_vcs = cmd_new_vcs_template<>;
diff --git a/bdep/new.cli b/bdep/new.cli
index 8f64e8f..1e206df 100644
--- a/bdep/new.cli
+++ b/bdep/new.cli
@@ -81,8 +81,8 @@ namespace bdep
$ bdep init -C @gcc cc config.cxx=g++
\
- After executing these commands the \c{hello} project will contain two
- packages, \c{libhello} and \c{hello}.
+ After executing these commands the \cb{hello} project will contain two
+ packages, \cb{libhello} and \cb{hello}.
The \cb{--subdirectory} form operates \i{as-if} by first creating
according to <spec> a temporary project called <name> and then copying
@@ -102,8 +102,8 @@ namespace bdep
$ bdep init -C @gcc cc config.cxx=g++
\
- After executing these commands the \c{hello} project will contain two
- source subdirectories, \c{libhello/} and \c{hello/}.
+ After executing these commands the \cb{hello} project will contain two
+ source subdirectories, \cb{libhello/} and \cb{hello/}.
The project parameters such as type (executable, library, etc), language,
and version control system can be customized as described next. Some of
@@ -265,6 +265,18 @@ namespace bdep
\li|\cb{none}
Don't initialize a version control system inside the project.||
+
+ The newly created project, package, or subdirectory can be further
+ customized using the post-creation hooks specified with the
+ \cb{--post-hook} option. The hook commands are executed in the
+ newly created project, package, or source directory as their
+ current working directory. For example:
+
+ \
+ $ bdep new --post-hook \"echo .idea/ >>.gitignore\" hello
+ \
+
+ See the \cb{--post-hook} option documentation below for details.
"
}
@@ -387,6 +399,52 @@ namespace bdep
system-specific."
}
+ strings --post-hook
+ {
+ "<command>",
+ "Run the specified command in the newly created project, package, or
+ source directory.
+
+ The <command> value is interpreted as a whitespace-separated,
+ potentially quoted command line consisting of the program optionally
+ followed by arguments and redirects. Specifically, a single level of
+ quotes (either single or double) is removed and whitespaces are not
+ treated as separators inside such quoted fragments. Currently only the
+ \cb{stdout} redirect to a file is supported. For example:
+
+ \
+ $ bdep new --post-hook \"echo '.idea/ # IDE' >>.gitignore\" hello
+ \
+
+ The command line elements (program, arguments, etc) may optionally
+ contain substitutions \- variable names enclosed with the \cb{@}
+ substitution symbol \- which are replaced with the corresponding
+ variable values to produce the actual command. The following variable
+ names are recognized with the double substitution symbol (\cb{@@})
+ serving as an escape sequence.
+
+ \
+ @mode@ - one of 'project', 'package', or 'subdirectory'
+ @name@ - project, package, or subdirectory name
+ @base@ - name base (name without extension)
+ @stem@ - name stem (name base without 'lib' prefix)
+ @type@ - type (--type|-t value: 'exe', 'lib', etc)
+ @lang@ - language (--lang|-l value: 'c', 'c++', etc)
+ @vcs@ - version control system (--vcs|-s value: 'git', etc)
+ @root@ - project/package root directory
+ \
+
+ For example:
+
+ \
+ $ bdep new --post-hook \"echo bin/ >>@name@/.gitignore\" hello
+ \
+
+ These substitution variables are also made available to the hook program
+ as the \cb{BDEP_NEW_*} environment variables (\cb{BDEP_NEW_MODE},
+ \cb{BDEP_NEW_NAME}, etc)."
+ }
+
// @@ This should be a no-amalgamation type sub-options.
//
bool --no-amalgamation
diff --git a/bdep/new.cxx b/bdep/new.cxx
index 7d1815c..55b6f8c 100644
--- a/bdep/new.cxx
+++ b/bdep/new.cxx
@@ -6,6 +6,7 @@
#include <algorithm> // replace()
+#include <libbutl/command.mxx>
#include <libbutl/project-name.mxx>
#include <bdep/project.hxx>
@@ -17,11 +18,10 @@
#include <bdep/config.hxx>
using namespace std;
+using namespace butl;
namespace bdep
{
- using butl::project_name;
-
using type = cmd_new_type;
using lang = cmd_new_lang;
using vcs = cmd_new_vcs;
@@ -1596,6 +1596,76 @@ namespace bdep
fail << "unable to write " << f << ": " << e;
}
+ // Run post-hooks.
+ //
+ optional<process_env> env;
+ optional<command_substitution_map> subs;
+ strings vars;
+
+ if (!o.post_hook ().empty ())
+ {
+ subs = command_substitution_map ();
+
+ auto add_var = [&subs, &vars] (string name, string value)
+ {
+ vars.push_back ("BDEP_NEW_" +
+ ucase (const_cast<const string&> (name)) +
+ '=' +
+ value);
+
+ (*subs)[move (name)] = move (value);
+ };
+
+ add_var ("mode", sub ? "subdirectory" : pkg ? "package" : "project");
+ add_var ("name", n);
+ add_var ("base", move (b));
+ add_var ("stem", move (s));
+ add_var ("type", t.string ());
+ add_var ("lang", l.string ());
+ add_var ("vcs", vc.string ());
+ add_var ("root", prj.string ());
+
+ env = process_env (process_path (), out, vars);
+ }
+
+ for (const string& cmd: o.post_hook ())
+ {
+ try
+ {
+ // Note: out directory path is absolute and normalized.
+ //
+ process_exit e (command_run (cmd,
+ env,
+ subs,
+ '@',
+ [] (const char* const args[], size_t n)
+ {
+ if (verb >= 2)
+ {
+ print_process (args, n);
+ }
+ }));
+
+ if (!e)
+ {
+ if (e.normal ())
+ throw failed (); // Assume the command issued diagnostics.
+
+ fail << "post hook '" << cmd << "' " << e;
+ }
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "invalid post hook '" << cmd << "': " << e;
+ }
+ // Handle process_error and io_error (both derive from system_error).
+ //
+ catch (const system_error& e)
+ {
+ fail << "unable to execute post hook '" << cmd << "': " << e;
+ }
+ }
+
if (verb)
text << "created new " << t << ' ' << (sub ? "source subdirectory" :
pkg ? "package" : "project")
diff --git a/tests/new.testscript b/tests/new.testscript
index 7e484fc..186c7e7 100644
--- a/tests/new.testscript
+++ b/tests/new.testscript
@@ -4,6 +4,12 @@
.include common.testscript
+posix = ($cxx.target.class != 'windows')
+
+# Our C tests use fmemopen() which is not always available.
+#
+c_tests = ($c.target.class != 'windows' && $c.target.class != 'macos')
+
# Disable nesting checks in the created projects.
#
test.arguments += --no-checks
@@ -154,12 +160,9 @@ status += -d prj
EOE
}
- # Our C tests use fmemopen() which is not always available.
- #
-
: lib-c
:
- if ($c.target.class != 'windows' && $c.target.class != 'macos')
+ if $c_tests
{
$* -t lib -l c libprj-foo 2>>/"EOE" &libprj-foo/***;
created new library project libprj-foo in $~/libprj-foo/
@@ -172,7 +175,7 @@ status += -d prj
: lib-c-unit-tests
:
- if ($c.target.class != 'windows' && $c.target.class != 'macos')
+ if $c_tests
{
$* -t lib,unit-tests -l c libfoo 2>>/"EOE" &libfoo/***;
created new library project libfoo in $~/libfoo/
@@ -500,7 +503,7 @@ status += -d prj
: type
:
- if ($c.target.class != 'windows' && $c.target.class != 'macos')
+ if $c_tests
{
: exe
:
@@ -584,7 +587,7 @@ status += -d prj
: c
:
- if ($c.target.class != 'windows' && $c.target.class != 'macos')
+ if $c_tests
{
$* -l c libprj 2>>/"EOE" &libprj/***;
created new library project libprj in $~/libprj/
@@ -613,6 +616,91 @@ status += -d prj
}
}
}
+
+ : post-hook
+ :
+ if $posix
+ {
+ : success
+ :
+ {
+ $* -t empty prj \
+ --post-hook "echo .idea/ >>.gitignore" \
+ --post-hook "echo @mode@ @name@ @base@ @stem@" \
+ --post-hook "echo @type@ @lang@ @vcs@ @root@" \
+ >>"EOO" 2>>/"EOE" &prj/***;
+ project prj prj prj
+ empty c++ git $~/prj
+ EOO
+ created new empty project prj in $~/prj/
+ EOE
+
+ cat prj/.gitignore >>~%EOO%;
+ %.+
+ .idea/
+ EOO
+
+ $* --package prj -d prj \
+ --post-hook "echo @@@@TODO >>NEWS" \
+ --post-hook "echo @mode@ @name@ @base@ @stem@ @root@" \
+ >>"EOO" 2>>/"EOE" &prj/prj/***;
+ package prj prj prj $~/prj
+ EOO
+ created new executable package prj in $~/prj/prj/
+ EOE
+
+ cat prj/prj/NEWS >'@@TODO';
+
+ $* --package -t lib libprj.bash -d prj \
+ --post-hook "echo @@@@TODO >>NEWS" \
+ --post-hook "echo @mode@ @name@ @base@ @stem@ @root@" \
+ >>"EOO" 2>>/"EOE" &prj/prj/***;
+ package libprj.bash libprj prj $~/prj
+ EOO
+ created new library package libprj.bash in $~/prj/libprj.bash/
+ EOE
+
+ cat prj/libprj.bash/NEWS >'@@TODO';
+
+ cat <<EOI >=hook;
+ #!/bin/sh
+ echo "$(pwd)"
+ echo "$BDEP_NEW_MODE $BDEP_NEW_ROOT"
+ EOI
+
+ chmod u+x hook;
+
+ $* -t lib --subdirectory libprj -d prj/prj --post-hook "$~/hook" \
+ >>"EOO" 2>>/"EOE" &prj/prj/libprj/***
+ $~/prj/prj/libprj
+ subdirectory $~/prj/prj
+ EOO
+ created new library source subdirectory libprj in $~/prj/prj/libprj/
+ EOE
+ }
+
+ : failure
+ :
+ {
+ : substitution
+ :
+ $* prj --post-hook 'echo foo >>@bar@' 2>>EOE &prj/*** != 0
+ error: invalid post hook 'echo foo >>@bar@': unknown variable 'bar'
+ EOE
+
+ : open-redirect
+ :
+ $* prj --post-hook 'echo foo >>bar/baz' 2>>/~%EOE% &prj/*** != 0
+ %error: unable to execute post hook 'echo foo >>bar/baz': unable to open stdout redirect file '.+baz': .+%
+ EOE
+
+ : process
+ :
+ $* prj --post-hook '""' 2>>~%EOE% &prj/*** != 0
+ %error: unable to execute post hook '""': .+%
+ EOE
+ }
+ }
}
: cfg