diff options
-rw-r--r-- | bdep/new-types.hxx | 50 | ||||
-rw-r--r-- | bdep/new.cli | 66 | ||||
-rw-r--r-- | bdep/new.cxx | 74 | ||||
-rw-r--r-- | tests/new.testscript | 102 |
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 |