diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2020-07-08 21:48:05 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2020-07-16 15:46:43 +0300 |
commit | 3aa122ed0fd598c4854b3d55f775e06a59112151 (patch) | |
tree | f776b3c08dd67359b43984e27769bcc082e454bb | |
parent | c004b0f6b8dd0ede917b90060fb0c97a91a239d2 (diff) |
Add support for 'prefix*', 'split' and 'no-subdir*' project type sub-options
Also:
- rename --subdirectory to --source
- rename --type|-t,source to subdir
- change the hook's mode variable value 'subdirectory' to 'source'
- rename bdep-new-subdirectory.options to bdep-new-source.options
- add src, inc, pfx, and sub pre-/post-hooks variables
-rw-r--r-- | bdep/new.cli | 527 | ||||
-rw-r--r-- | bdep/new.cxx | 4261 | ||||
-rw-r--r-- | tests/new.testscript | 533 |
3 files changed, 3405 insertions, 1916 deletions
diff --git a/bdep/new.cli b/bdep/new.cli index 93a226f..3508465 100644 --- a/bdep/new.cli +++ b/bdep/new.cli @@ -26,7 +26,7 @@ namespace bdep \b{bdep new} [<options>] \b{--config-create|-C} <cfg-dir> [\b{@}<cfg-name>] <spec> [<name>]\n \ \ \ \ \ \ \ \ \ [<cfg-args>]\n \b{bdep new} [<options>] \b{--package} [<prj-spec>] <spec> [<name>]\n - \b{bdep new} [<options>] \b{--subdirectory} [<prj-spec>] <spec> [<name>]} + \b{bdep new} [<options>] \b{--source} [<prj-spec>] <spec> [<name>]} \c{<spec> \ \ \ \ = [<lang>] [<type>] [<vcs>]\n <lang> \ \ \ \ = \b{--lang}|\b{-l} (\b{c}|\b{c++})[\b{,}<lang-opt>...]\n @@ -40,8 +40,8 @@ namespace bdep The \cb{new} command creates and initializes a new project (the first three forms), a new package in an already existing project (the \cb{--package} form), or a new source subdirectory in an already existing - project/package (the \cb{--subdirectory} form). All the forms except - \cb{--subdirectory} first create according to <spec> a new \cb{build2} + project/package (the \cb{--source} form). All the forms except + \cb{--source} first create according to <spec> a new \cb{build2} project/package called <name> in the <name> subdirectory of the current working directory (unless overridden with \c{\b{--output-dir}|\b{-o}}). @@ -51,6 +51,14 @@ namespace bdep \ $ bdep new -l c++ -t exe hello + + $ tree hello/ + hello/ + ├── hello/ + │ ├── hello.cxx + │ └── buildfile + ├── buildfile + └── manifest \ Similarly, the second and third forms add an existing or create new build @@ -76,15 +84,30 @@ namespace bdep $ bdep new --package -l c++ -t exe hello $ bdep init -C @gcc cc config.cxx=g++ - \ - After executing these commands the \cb{hello} project will contain two - packages, \cb{libhello} and \cb{hello}. + $ cd .. + $ tree hello/ + hello/ + ├── hello/ + │ ├── hello/ + │ │ ├── hello.cxx + │ │ └── buildfile + │ ├── buildfile + │ └── manifest + ├── libhello/ + │ ├── libhello/ + │ │ ├── hello.hxx + │ │ ├── hello.cxx + │ │ └── buildfile + │ ├── buildfile + │ └── manifest + └── packages.manifest + \ - The \cb{--subdirectory} form operates \i{as-if} by first creating - according to <spec> a temporary project called <name> and then copying - its source subdirectory (\c{\i{name}\b{/}\i{name}\b{/}} by default) over - to the current working directory (unless overridden with + The \cb{--source} form operates \i{as-if} by first creating according to + <spec> a temporary project called <name> and then copying its source + subdirectory (\c{\i{name}\b{/}\i{name}\b{/}} by default) over to the + current working directory (unless overridden with \c{\b{--output-dir}|\b{-o}}). If no project/package directory is explicitly specified with \c{\b{--directory}|\b{-d}}, then the current working directory is assumed. For example: @@ -93,30 +116,78 @@ namespace bdep $ bdep new -l c++ -t bare hello $ cd hello - $ bdep new --subdirectory -l c++ -t lib libhello - $ bdep new --subdirectory -l c++ -t exe hello + $ bdep new --source -l c++ -t lib libhello + $ bdep new --source -l c++ -t exe hello $ bdep init -C @gcc cc config.cxx=g++ - \ - After executing these commands the \cb{hello} project will contain two - source subdirectories, \cb{libhello/} and \cb{hello/}. + $ cd .. + $ tree hello/ + hello/ + ├── hello/ + │ ├── hello.cxx + │ └── buildfile + ├── libhello/ + │ ├── hello.hxx + │ ├── hello.cxx + │ └── buildfile + ├── buildfile + └── manifest + \ In all the forms, if <name> is omitted, then the current working directory name (unless overridden with \c{\b{--output-dir}|\b{-o}}) is - used as the project/package/subdirectory name. See \l{bpkg#package-name - Package Name} for details on project/package names. + used as the project/package/source subdirectory name. See + \l{bpkg#package-name Package Name} for details on project/package names. - The source subdirectory can be customized with the \cb{source} project + The source subdirectory can be customized with the \cb{subdir} project type sub-option (see below for details). For example: \ - $ bdep new -l c++ -t lib,source=libhello/io libhello-io + $ bdep new -l c++ -t lib,subdir=libhello/io libhello-io + + $ tree libhello-io/ + libhello-io/ + └── libhello/ + └── io/ + ├── hello-io.hxx + └── hello-io.cxx \ - After executing this command the \cb{libhello-io} project will contain - the \cb{libhello/io/} source subdirectory instead of the default - \cb{libhello-io/}. + By default the source subdirectory is created in the project/package root + directory and contains both headers (including public headers for + libraries) as well as sources. This can be customized in a number of ways + using the \cb{prefix*} and \cb{split} project type sub-options (see below + for details). For example, to move the source subdirectory inside + \c{src/}: + + \ + $ bdep new -l c++ -t exe,prefix=src hello + + $ tree hello/ + hello/ + └── src/ + └── hello/ + └── hello.cxx + \ + + And to split the library source subdirectory into public headers and + other source files: + + \ + $ bdep new -l c++ -t lib,split libhello + + $ tree libhello/ + libhello/ + ├── include/ + │ └── libhello/ + │ └── hello.hxx + └── src/ + └── libhello/ + └── hello.cxx + \ + + See the SOURCE LAYOUT section for details and more examples. The output directory may already contain existing files provided they don't clash with the files to be created. The \cb{new} command also @@ -229,9 +300,17 @@ namespace bdep Don't add support for installing.| - \li|\n\ \ \ \c{\b{source=}\i{dir}} + \li|\n\ \ \ \c{\b{prefix=}\i{dir}} + + Optional source prefix relative to project/package root.| - Alternative source subdirectory relative to project/package root.| + \li|\n\ \ \ \c{\b{subdir=}\i{dir}} + + Alternative source subdirectory relative to source prefix.| + + \li|\n\ \ \ \cb{no-subdir} + + Omit the source subdirectory.| \li|\n\ \ \ \c{\b{license=}\i{name}}| @@ -268,9 +347,34 @@ namespace bdep Don't add support for generating the version header.| - \li|\n\ \ \ \c{\b{source=}\i{dir}} + \li|\n\ \ \ \c{\b{prefix-include=}\i{dir}} + + Optional public header prefix relative to project/package root.| + + \li|\n\ \ \ \c{\b{prefix-source=}\i{dir}} + + Optional source prefix relative to project/package root.| + + \li|\n\ \ \ \c{\b{prefix=}\i{dir}} + + Shortcut for \c{\b{prefix-include=}\i{dir}\b{,prefix-source=}\i{dir}}.| + + \li|\n\ \ \ \cb{split} + + Shortcut for \cb{prefix-include=include,prefix-source=src}.| + + \li|\n\ \ \ \c{\b{subdir=}\i{dir}} + + Alternative source subdirectory relative to header/source prefix.| + + \li|\n\ \ \ \cb{no-subdir} + + Omit the source subdirectory.| + + \li|\n\ \ \ \cb{no-subdir-source} - Alternative source subdirectory relative to project/package root.| + Omit the source subdirectory relative to the source prefix but still + create one relative to the header prefix.| \li|\n\ \ \ \c{\b{license=}\i{name}}| @@ -285,7 +389,7 @@ namespace bdep \li|\cb{bare} A project without any source code that can be filled later - (see \cb{--subdirectory}). Recognized bare project sub-options:| + (see \cb{--source}). Recognized bare project sub-options:| \li|\n\ \ \ \cb{no-tests} @@ -391,13 +495,13 @@ namespace bdep Don't initialize a version control system inside the project.|| - The created project, package, or subdirectory can be further customized - using the pre and post-creation hooks specified with the \cb{--pre-hook} - and \cb{--post-hook} options, respectively. The pre hooks are executed - before any new files are created and the post hook \- after all the files - have been created. The hook commands are executed in the project, - package, or source directory as their current working directory. For - example: + The created project, package, or source subdirectory can be further + customized using the pre and post-creation hooks specified with the + \cb{--pre-hook} and \cb{--post-hook} options, respectively. The pre hooks + are executed before any new files are created and the post hook \- after + all the files have been created. The hook commands are executed in the + project, package, or source directory as their current working directory. + For example: \ $ bdep new --post-hook \"echo .idea/ >>.gitignore\" hello @@ -438,17 +542,27 @@ namespace bdep string mxx; }; - //--type options + //--type options + // + // Note that we only support combined executable source layouts. // class cmd_new_exe_options { bool no-tests; bool unit-tests; bool no-install; - dir_path "source"; + dir_path prefix; + dir_path subdir; + bool no-subdir; string license = "other: proprietary"; bool no-readme; bool alt-naming; + + // Old name for the subdir sub-option (thus undocumented). + // + // If specified, we will fail suggesting to use the new sub-option instead. + // + dir_path "source"; }; class cmd_new_lib_options @@ -458,10 +572,22 @@ namespace bdep bool unit-tests; bool no-install; bool no-version; - dir_path "source"; + dir_path prefix-source; + dir_path prefix-include; + dir_path prefix; + bool split; + dir_path subdir; + bool no-subdir; + bool no-subdir-source; string license = "other: proprietary"; bool no-readme; bool alt-naming; + + // Old name for the subdir sub-option (thus undocumented). + // + // If specified, we will fail suggesting to use the new sub-option instead. + // + dir_path "source"; }; class cmd_new_bare_options @@ -503,12 +629,18 @@ namespace bdep new project." } - bool --subdirectory + bool --source { "Create a new source subdirectory inside an already existing project or package rather than a new project." } + // Old name for the --source option (thus undocumented). + // + // If specified, we will fail suggesting to use the new option instead. + // + bool --subdirectory; + dir_path --output-dir|-o { "<dir>", @@ -521,7 +653,7 @@ namespace bdep "<dir>", "Assume the project/package is in the specified directory rather than in the current working directory. Only used with \cb{--package} or - \cb{--subdirectory}." + \cb{--source}." } cmd_new_type --type|-t @@ -581,16 +713,24 @@ namespace bdep serving as an escape sequence. \ - @mode@ - one of 'project', 'package', or 'subdirectory' - @name@ - project, package, or subdirectory name + @mode@ - one of 'project', 'package', or 'source' + @name@ - project, package, or source subdirectory name @base@ - name base (name without extension) @stem@ - name stem (name base without 'lib' prefix) + @root@ - project/package root directory + @pfx@ - combined prefix relative to project/package root + @inc@ - split header prefix relative to project/package root + @src@ - split source prefix relative to project/package root + @sub@ - source subdirectory relative to header/source 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 \ + Note that the \cb{@inc@} and \cb{@src@} variables are only set if the + header/source prefix is split with the combined \cb{@pfx@} variable set + otherwise. + For example: \ @@ -630,21 +770,310 @@ namespace bdep }; " + \h#src-layout|SOURCE LAYOUT| + + C and C++ projects employ a bewildering variety of source code layouts most + of which fit into two broad classes: \i{combined} where all source code for + a single executable or library resides in the same directory and \i{split} + where headers (typically public headers of a library) and other source + files reside in separate directories (most commonly called \cb{include/} + and \cb{src/}). + + To support creation of such varying layouts the \cb{new} command divides + paths leading to source code inside a package/project into a number of + customizable components: + + \ + libhello/{include,src}/hello/ + ^ ^ ^ + | | | + project/ source source + package prefix subdirectory + root + \ + + Note that while the same physical layout can be achieved with various + combinations of source prefix and subdirectory, there will be differences + in semantics since the headers in the project are included with the source + subdirectory (if any) as a prefix. + + As we have already seen, the source subdirectory can be customized with the + \cb{subdir} project type sub-option. For example: + + \ + # libhello/hello/ + + $ bdep new -l c++ -t lib,subdir=hello libhello + + $ tree libhello/ + libhello/ + └── hello/ + ├── hello.hxx + └── hello.cxx + \ + + The source prefix can be combined, in which case it can be customized with + the single \cb{prefix} project type sub-option. For example: + + \ + # hello/src/hello/ + + $ bdep new -l c++ -t exe,prefix=src hello + + $ tree hello/ + hello/ + └── src/ + └── hello/ + └── hello.cxx + \ + + The prefix can also be split, in which case the \cb{prefix-include} and + \cb{prefix-source} sub-options can be used to customize the respective + directories independently. For example: + + \ + # libhello/{include,.}/libhello/ + + $ bdep new -l c++ -t lib,prefix-include=include libhello + + $ tree libhello/ + libhello/ + ├── include/ + │ └── libhello/ + │ └── hello.hxx + └── libhello/ + └── hello.cxx + \ + + The \cb{split} sub-option is a convenient shortcut for the most common case + where the header prefix is \cb{include/} and source prefix is \cb{src/}. For + example: + + \ + # libhello/{include,src}/libhello/ + + $ bdep new -l c++ -t lib,split libhello + + $ tree libhello/ + libhello/ + ├── include/ + │ └── libhello/ + │ └── hello.hxx + └── src/ + └── libhello/ + └── hello.cxx + \ + + The source subdirectory can be omitted by specifying the \c{no-subdir} + project type sub-option. For example: + + \ + # hello/src/ + + $ bdep new -l c++ -t exe,prefix=src,no-subdir hello + + $ tree hello/ + hello/ + └── src/ + └── hello.cxx + \ + + The same but for the split layout (we also have to disable generating the + version header that is not supported in this layout): + + \ + # libhello/{include,src}/ + + $ bdep new -l c++ -t lib,split,no-subdir,no-version libhello + + $ tree libhello/ + libhello/ + ├── include/ + │ └── hello.hxx + └── src/ + └── hello.cxx + \ + + To achieve the layout where all the source code resides in the project + root, we omit both the source prefix and subdirectory (we also have to + disable a couple of other features that are not supported in this layout): + + \ + # hello/ + + $ bdep new -l c++ -t lib,no-subdir,no-version,no-tests libhello + + $ tree libhello/ + libhello/ + ├── hello.cxx + └── hello.hxx + \ + + We can also omit the source subdirectory but only in the source prefix of + the split layout by specifying the \c{no-subdir-source} sub-option. For + example: + + \ + # libhello/{include/hello,src}/ + + $ bdep new -l c++ -t lib,split,subdir=hello,no-subdir-source libhello + + $ tree libhello/ + libhello/ + ├── include/ + │ └── hello/ + │ └── hello.hxx + └── src/ + └── hello.cxx + \ + + To achieve the split layout where the \c{include/} directory is inside + \c{src/}: + + \ + # libhello/src/{include,.}/hello/ + + $ bdep new \ + -l c++ \ + -t lib,prefix-include=src/include,prefix-source=src,subdir=hello \ + libhello + + $ tree libhello/ + libhello/ + └── src/ + ├── hello/ + │ └── hello.cxx + └── include/ + └── hello/ + └── hello.hxx + \ + + A similar layout but without the source subdirectory in \c{src/}: + + \ + # libhello/src/{include/hello,.}/ + + $ bdep new \ + -l c++ \ + -t lib,prefix-include=src/include,prefix-source=src,\ + subdir=hello,no-subdir-source \ + libhello + + $ tree libhello/ + libhello/ + └── src/ + ├── include/ + │ └── hello/ + │ └── hello.hxx + └── hello.cxx + \ + + The layout used by the Boost libraries: + + \ + # libhello/{include/hello,libs/hello/src}/ + + $ bdep new \ + -l c++ \ + -t lib,prefix-include=include,prefix-source=libs/hello/src,\ + subdir=hello,no-subdir-source \ + libhello + + $ tree libhello/ + libhello/ + ├── include/ + │ └── hello/ + │ └── hello.hxx + └── libs/ + └── hello/ + └── src/ + └── hello.cxx + \ + + A layout where multiple components each have their own \c{include/src} + split: + + \ + # hello/libhello1/{include/hello1,src}/ + # hello/libhello2/{include/hello2,src}/ + + $ bdep new -l c++ -t bare hello + + $ bdep new -d hello --source \ + -l c++ \ + -t lib,\ + prefix-include=libhello1/include,prefix-source=libhello1/src,\ + subdir=hello1,no-subdir-source \ + libhello1 + + $ bdep new -d hello --source \ + -l c++ \ + -t lib,\ + prefix-include=libhello2/include,prefix-source=libhello2/src,\ + subdir=hello2,no-subdir-source \ + libhello2 + + $ tree hello/ + hello/ + ├── libhello1/ + │ ├── include/ + │ │ └── hello1/ + │ │ └── hello1.hxx + │ └── src/ + │ └── hello1.cxx + └── libhello2/ + ├── include/ + │ └── hello2/ + │ └── hello2.hxx + └── src/ + └── hello2.cxx + \ + + A layout where libraries and executables have different prefixes: + + \ + # hello/libs/libhello/{include/hello,src}/ + # hello/src/hello/ + + $ bdep new -l c++ -t bare hello + + $ bdep new -d hello --source \ + -l c++ \ + -t lib,\ + prefix-include=libs/libhello/include,prefix-source=libs/libhello/src,\ + subdir=hello,no-subdir-source \ + libhello + + $ bdep new -d hello --source -l c++ -t exe,prefix=src hello + + $ tree hello/ + hello/ + ├── libs/ + │ └── libhello/ + │ ├── include/ + │ │ └── hello/ + │ │ └── hello.hxx + │ └── src/ + │ └── hello.cxx + └── src/ + └── hello/ + └── hello.cxx + \ + \h|DEFAULT OPTIONS FILES| See \l{bdep-default-options-files(1)} for an overview of the default options files. For the \cb{new} command the search start directory is the - project directory in the package and subdirectory modes and the parent - directory of the new project in all other modes. The following options - files are searched for in each directory and, if found, loaded in the - order listed: + project directory in the package and source modes and the parent directory + of the new project in all other modes. The following options files are + searched for in each directory and, if found, loaded in the order listed: \ bdep.options bdep-{config config-add}.options # if --config-add|-A bdep-{config config-add config-create}.options # if --config-create|-C bdep-new.options - bdep-new-{project|package|subdirectory}.options # (mode-dependent) + bdep-new-{project|package|source}.options # (mode-dependent) \ The following \cb{new} command options cannot be specified in the @@ -654,7 +1083,7 @@ namespace bdep --output-dir|-o --directory|-d --package - --subdirectory + --source --no-checks --config-add|-A --config-create|-C @@ -671,6 +1100,6 @@ namespace bdep package email address. If not set, the \cb{new} command will first try to obtain the email from the version control system (if used) and then from the \cb{EMAIL} environment variable. If all these methods fail, a dummy - \cb{@example.org} email is used. + \cb{you@example.org} email is used. " } diff --git a/bdep/new.cxx b/bdep/new.cxx index 318d4aa..4be2239 100644 --- a/bdep/new.cxx +++ b/bdep/new.cxx @@ -65,19 +65,18 @@ namespace bdep {"other: proprietary", "Not free/open source" }, {"other: TODO", "License is not yet decided" }}; - - // Extract a license id from a license file returning an empty string if - // it doesn't match any known license file signatures. + // Extract a license id from a license file returning an empty string if it + // doesn't match any known license file signatures. // static string extract_license (const path& f) { - // The overall plan is to read the license heading and then try to match - // it against a bunch of regular expression. + // The overall plan is to read the license heading and then try to match it + // against a bunch of regular expression. // // Some license headings are spread over multiple lines but all the files - // that we have seen so far separate the heading from the license body - // with a blank line, for example: + // that we have seen so far separate the heading from the license body with + // a blank line, for example: // // Apache License // Version 2.0, January 2004 @@ -144,9 +143,9 @@ namespace bdep return p.second; }; - // Note that some licenses (for example, GNU licenses) don't spell the - // zero minor version. So for them we may need to provide two properly - // ordered regular expressions. + // Note that some licenses (for example, GNU licenses) don't spell the zero + // minor version. So for them we may need to provide two properly ordered + // regular expressions. // (test ("MIT License", "MIT") || test ("BSD ([1234])-Clause License", "BSD-$1-Clause") || @@ -233,2292 +232,2856 @@ namespace bdep using type = cmd_new_type; using lang = cmd_new_lang; using vcs = cmd_new_vcs; +} + +int bdep:: +cmd_new (cmd_new_options&& o, cli::group_scanner& args) +{ + tracer trace ("new"); + + // Validate options. + // + bool ca (o.config_add_specified ()); + bool cc (o.config_create_specified ()); + + if (o.subdirectory ()) + fail << "--subdirectory was renamed to --source"; + + if (o.package () && o.source ()) + fail << "both --package and --source specified"; - int - cmd_new (cmd_new_options&& o, cli::group_scanner& args) + const char* m (o.package () ? "--package" : + o.source () ? "--source" : nullptr); + + if (m && o.no_init ()) + fail << "both --no-init and " << m << " specified"; + + if (const char* n = (o.no_init () ? "--no-init" : + m ? m : nullptr)) { - tracer trace ("new"); + if (ca) fail << "both " << n << " and --config-add specified"; + if (cc) fail << "both " << n << " and --config-create specified"; + } - // Validate options. - // - bool ca (o.config_add_specified ()); - bool cc (o.config_create_specified ()); + if (o.directory_specified () && !m) + fail << "--directory|-d only valid with --package or --source"; - if (o.package () && o.subdirectory ()) - fail << "both --package and --subdirectory specified"; + if (const char* n = cmd_config_validate_add (o)) + { + if (!ca && !cc) + fail << n << " specified without --config-(add|create)"; + + if (o.existing () && !cc) + fail << "--existing|-e specified without --config-create"; - const char* m (o.package () ? "--package" : - o.subdirectory () ? "--subdirectory" : nullptr); + if (o.wipe () && !cc) + fail << "--wipe specified without --config-create"; + } - if (m && o.no_init ()) - fail << "both --no-init and " << m << " specified"; + // Validate language options. + // + const lang& l (o.lang ()); - if (const char* n = (o.no_init () ? "--no-init" : - m ? m : nullptr)) + switch (l) + { + case lang::c: { - if (ca) fail << "both " << n << " and --config-add specified"; - if (cc) fail << "both " << n << " and --config-create specified"; + break; } + case lang::cxx: + { + auto& o (l.cxx_opt); - if (o.directory_specified () && !m) - fail << "--directory|-d only valid with --package or --subdirectory"; + if (o.cpp () && o.extension_specified ()) + fail << "'extension' and 'cpp' are mutually exclusive c++ options"; - if (const char* n = cmd_config_validate_add (o)) - { - if (!ca && !cc) - fail << n << " specified without --config-(add|create)"; + // Verify that none of the extensions are specified as empty, except for + // hxx. + // + auto empty_ext = [] (const string& v, const char* o) + { + if (v.empty () || (v.size () == 1 && v[0] == '.')) + fail << "empty extension specified with '" << o << "' c++ option"; + }; - if (o.existing () && !cc) - fail << "--existing|-e specified without --config-create"; + if (o.extension_specified ()) empty_ext (o.extension (), "extension"); - if (o.wipe () && !cc) - fail << "--wipe specified without --config-create"; + if (o.cxx_specified ()) empty_ext (o.cxx (), "cxx"); + if (o.ixx_specified ()) empty_ext (o.ixx (), "ixx"); + if (o.txx_specified ()) empty_ext (o.txx (), "txx"); + if (o.mxx_specified ()) empty_ext (o.mxx (), "mxx"); + + break; } + } - // Validate language options. - // - const lang& l (o.lang ()); + // Validate type options. + // + const type& t (o.type ()); - switch (l) + if ((t == type::exe && t.exe_opt.source_specified ()) || + (t == type::lib && t.lib_opt.source_specified ())) + fail << "--type|-t,source was renamed to --type|-t,subdir"; + + // For a library source subdirectory (--source) we don't generate the export + // stub, integration tests (because there is no export stub), or the version + // header (because the project name used in the .in file will most likely be + // wrong). All this seems reasonable for what this mode is expected to be + // used ("end-product" kind of projects). + // + bool readme (false); // !no-readme + bool altn (false); // alt-naming + bool itest (false); // !no-tests + bool utest (false); // unit-tests + bool install (false); // !no-install + bool ver (false); // !no-version + + string license; + bool license_o (false); + { + bool pkg (o.package ()); + bool src (o.source ()); + + switch (t) { - case lang::c: + case type::exe: { + readme = !t.exe_opt.no_readme () && !src; + altn = t.exe_opt.alt_naming (); + itest = !t.exe_opt.no_tests (); + utest = t.exe_opt.unit_tests (); + install = !t.exe_opt.no_install (); + + if (!src) + { + license = t.exe_opt.license (); + license_o = t.exe_opt.license_specified (); + } break; } - case lang::cxx: + case type::lib: { - auto& o (l.cxx_opt); + if (t.lib_opt.binless () && l != lang::cxx) + fail << "--type|-t,binless is only valid for C++ libraries"; - if (o.cpp () && o.extension_specified ()) - fail << "'extension' and 'cpp' are mutually exclusive c++ options"; + readme = !t.lib_opt.no_readme () && !src; + altn = t.lib_opt.alt_naming (); + itest = !t.lib_opt.no_tests () && !src; + utest = t.lib_opt.unit_tests (); + install = !t.lib_opt.no_install (); + ver = !t.lib_opt.no_version () && !src; - // Verify that none of the extensions are specified as empty, except - // for hxx. - // - auto empty_ext = [] (const string& v, const char* o) + if (!src) { - if (v.empty () || (v.size () == 1 && v[0] == '.')) - fail << "empty extension specified with '" << o << "' c++ option"; - }; + license = t.lib_opt.license (); + license_o = t.lib_opt.license_specified (); + } + break; + } + case type::bare: + { + if (src) + fail << "cannot create bare source subdirectory"; - if (o.extension_specified ()) empty_ext (o.extension (), "extension"); + readme = !t.bare_opt.no_readme (); + altn = t.bare_opt.alt_naming (); + itest = !t.bare_opt.no_tests (); + install = !t.bare_opt.no_install (); - if (o.cxx_specified ()) empty_ext (o.cxx (), "cxx"); - if (o.ixx_specified ()) empty_ext (o.ixx (), "ixx"); - if (o.txx_specified ()) empty_ext (o.txx (), "txx"); - if (o.mxx_specified ()) empty_ext (o.mxx (), "mxx"); + if (!src) + { + license = t.bare_opt.license (); + license_o = t.bare_opt.license_specified (); + } + break; + } + case type::empty: + { + if (const char* w = (src ? "source subdirectory" : + pkg ? "package" : nullptr)) + fail << "cannot create empty " << w; + readme = !t.empty_opt.no_readme (); break; } } + } - // Validate type options. - // - const type& t (o.type ()); + // Standard/alternative build file/directory naming scheme. + // + const dir_path build_dir (altn ? "build2" : "build"); + const string build_ext (altn ? "build2" : "build"); + const path buildfile_file (altn ? "build2file" : "buildfile"); - // For a library source subdirectory (--subdirectory) we don't generate - // the export stub, integration tests (because there is no export stub), - // or the version header (because the project name used in the .in file - // will most likely be wrong). All this seems reasonable for what this - // mode is expected to be used ("end-product" kind of projects). - // - bool readme (false); // !no-readme - bool altn (false); // alt-naming - bool itest (false); // !no-tests - bool utest (false); // unit-tests - bool install (false); // !no-install - bool ver (false); // !no-version - - string license; - bool license_o (false); + // User-supplied source subdirectory (--type,subdir). + // + // Should we derive the C++ namespace from this (e.g., foo::bar from + // libfoo/bar) and allow its customization (e.g., --type,namespace)? That + // was the initial impulse but doing this will complicate things quite a + // bit. In particular, we will have to handle varying indentation levels. + // On the other hand, our goal is not to produce a project that requires an + // absolute minimum of changes but rather a project that is easy to + // tweak. And changing the namespace is straightforward (unlike changing the + // source subdirectory, which appears in quite a few places). So let's keep + // it simple for now. + // + const dir_path* subdir (t == type::exe ? (t.exe_opt.subdir_specified () + ? &t.exe_opt.subdir () + : nullptr) : + t == type::lib ? (t.lib_opt.subdir_specified () + ? &t.lib_opt.subdir () + : nullptr) : + nullptr); + + bool sub_inc; // false if the header subdirectory is omitted. + bool sub_src; // false if the source subdirectory is omitted. + { + bool no_subdir (t == type::exe ? t.exe_opt.no_subdir () : + t == type::lib ? t.lib_opt.no_subdir () : + false); + + bool no_subdir_src (t == type::lib && t.lib_opt.no_subdir_source ()); + + if (no_subdir) { - bool pkg (o.package ()); - bool sub (o.subdirectory ()); + if (subdir != nullptr) + fail << "both --type|-t,subdir and --type|-t,no-subdir specified"; - switch (t) - { - case type::exe: - { - readme = !t.exe_opt.no_readme () && !sub; - altn = t.exe_opt.alt_naming (); - itest = !t.exe_opt.no_tests (); - utest = t.exe_opt.unit_tests (); - install = !t.exe_opt.no_install (); + if (no_subdir_src) + fail << "both --type|-t,no-subdir and --type|-t,no-subdir-source " + << "specified"; - if (!sub) - { - license = t.exe_opt.license (); - license_o = t.exe_opt.license_specified (); - } - break; - } - case type::lib: - { - if (t.lib_opt.binless () && l != lang::cxx) - fail << "--type|-t,binless is only valid for C++ libraries"; + // Note that the generated header machinery requires the source + // subdirectory as a prefix for #include directive. Thus, the version + // header generation needs if no-subdir. + // + if (t == type::lib && !t.lib_opt.no_version ()) + fail << "generated version header is not supported in this layout" << + info << "specify --type|-t,no-version explicitly"; + } - readme = !t.lib_opt.no_readme () && !sub; - altn = t.lib_opt.alt_naming (); - itest = !t.lib_opt.no_tests () && !sub; - utest = t.lib_opt.unit_tests (); - install = !t.lib_opt.no_install (); - ver = !t.lib_opt.no_version () && !sub; + sub_inc = !no_subdir; + sub_src = !no_subdir && !no_subdir_src; - if (!sub) - { - license = t.lib_opt.license (); - license_o = t.lib_opt.license_specified (); - } - break; - } - case type::bare: - { - if (sub) - fail << "cannot create bare source subdirectory"; + // The header subdirectory can only be omited together with the source + // subdirectory. + // + assert (sub_inc || !sub_src); + } - readme = !t.bare_opt.no_readme (); - altn = t.bare_opt.alt_naming (); - itest = !t.bare_opt.no_tests (); - install = !t.bare_opt.no_install (); + if (subdir != nullptr && subdir->absolute ()) + fail << "absolute path " << *subdir << " specified for --type|-t,subdir"; - if (!sub) - { - license = t.bare_opt.license (); - license_o = t.bare_opt.license_specified (); - } - break; - } - case type::empty: - { - if (const char* w = (sub ? "source subdirectory" : - pkg ? "package" : nullptr)) - fail << "cannot create empty " << w; + // Validate vcs options. + // + vcs vc (o.vcs ()); + bool vc_o (o.vcs_specified ()); - readme = !t.empty_opt.no_readme (); - break; - } + // Check if we have the argument (name). If not, then we use the specified + // output or current working directory name. + // + string a; + if (args.more ()) + a = args.next (); + else + { + if (!o.output_dir_specified ()) + { + // Reduce this case (for the logic that follows) to as-if the current + // working directory was specified as the output directory. Unless we + // are in the source mode and the subdir sub-option was specified (see + // the relevant code below for the whole picture). + // + if (o.source () && subdir != nullptr) + a = subdir->leaf ().string (); + else + { + o.output_dir (path::current_directory ()); + o.output_dir_specified (true); } } - // Standard/alternative build file/directory naming scheme. - // - const dir_path build_dir (altn ? "build2" : "build"); - const string build_ext (altn ? "build2" : "build"); - const path buildfile_file (altn ? "build2file" : "buildfile"); + if (a.empty ()) + a = o.output_dir ().leaf ().string (); + } - // User-supplied source subdirectory (--type,source). - // - // Should we derive the C++ namespace from this (e.g., foo::bar from - // libfoo/bar) and allow its customization (e.g., --type,namespace)? That - // was the initial impulse but doing this will complicate things quite a - // bit. In particular, we will have to handle varying indentation levels. - // On the other hand, our goal is not to produce a project that requires - // an absolute minimum of changes but rather a project that is easy to - // tweak. And changing the namespace is straightforward (unlike changing - // the source subdirectory, which appears in quite a few places). So let's - // keep it simple for now. - // - const dir_path* source (t == type::exe ? (t.exe_opt.source_specified () - ? &t.exe_opt.source () - : nullptr) : - t == type::lib ? (t.lib_opt.source_specified () - ? &t.lib_opt.source () - : nullptr) : - nullptr); - - if (source != nullptr && source->absolute ()) - fail << "invalid value '" << *source << "' for option " - << "--type|-t,source: absolute path"; - - // Validate vcs options. - // - vcs vc (o.vcs ()); - bool vc_o (o.vcs_specified ()); + // If the project type is not empty then the project name is also a package + // name. But even if it is empty, verify it is a valid package name since it + // will most likely end up in the 'project' manifest value. + // + package_name pkgn; + + try + { + pkgn = package_name (move (a)); + } + catch (const invalid_argument& e) + { + fail << "invalid " << (t == type::empty ? "project" : "package") + << " name: " << e; + } - // Check if we have the argument (name). If not, then we use the specified - // output or current working directory name. + // Full package name vs base name (e.g., libhello in libhello.bash) vs the + // name stem (e.g, hello in libhello). + // + // We use the full name in the manifest and the top-level directory, the + // base name for inner filesystem directories and preprocessor macros, while + // the (sanitized) stem for modules, namespaces, etc. + // + const string& n (pkgn.string ()); // Full name. + const string& b (pkgn.base ()); // Base name. + const string& v (pkgn.variable ()); // Variable name. + string s (b); // Name stem. + { + // Warn about the lib prefix unless we are creating a source subdirectory, + // in which case the project is probably not meant to be a package anyway. // - string a; - if (args.more ()) - a = args.next (); - else + bool w (!o.source ()); + + switch (t) { - if (!o.output_dir_specified ()) + case type::exe: { - // Reduce this case (for the logic that follows) to as-if the current - // working directory was specified as the output directory. Unless we - // are in the subdirectory mode and the source sub-option was - // specified (see the relevant code below for the whole picture). - // - if (o.subdirectory () && source != nullptr) - a = source->leaf ().string (); - else + if (w && s.compare (0, 3, "lib") == 0) + warn << "executable name starts with 'lib'"; + + break; + } + case type::lib: + { + if (s.compare (0, 3, "lib") == 0) { - o.output_dir (path::current_directory ()); - o.output_dir_specified (true); + s.erase (0, 3); + + if (w && s.empty ()) + fail << "empty library name stem in '" << b << "'"; } + else if (w) + warn << "library name does not start with 'lib'"; + + break; } + case type::bare: + case type::empty: + break; + } + } - if (a.empty ()) - a = o.output_dir ().leaf ().string (); + // Sanitize the stem to be a valid language identifier. + // + string id; + switch (l) + { + case lang::c: + case lang::cxx: + { + id = sanitize_identifier (const_cast<const string&> (s)); + break; } + } - // If the project type is not empty then the project name is also a package - // name. But even if it is empty, verify it is a valid package name since - // it will most likely end up in the 'project' manifest value. - // - package_name pkgn; + // The anatomy and associated terminology of the paths inside the project: + // + // libfoo/{src,include}/libfoo + // ^ ^ ^ + // | | | + // project/ source source + // package prefix subdirectory + // root + + // Source prefix defaults to project/package root. + // + dir_path pfx_inc; + dir_path pfx_src; - try + // Calculate the effective source/include prefixes based on the project type + // 'prefix*' and 'split' sub-options. Fail if any mutually exclusive + // sub-options were specified. + // + // Note that for the executable project type the include and source prefixes + // are always the same. + // + switch (t) + { + case type::exe: { - pkgn = package_name (move (a)); + const cmd_new_exe_options& opt (t.exe_opt); + + if (opt.prefix_specified ()) + { + // In the source mode --output-dir|-o is the source subdirectory and + // so implies no source prefix. + // + if (o.source () && o.output_dir_specified ()) + fail << "both --output-dir|-o and --type|-t,prefix specified"; + + pfx_inc = pfx_src = opt.prefix (); + } + + break; } - catch (const invalid_argument& e) + case type::lib: { - fail << "invalid " << (t == type::empty ? "project" : "package") - << " name: " << e; - } + const cmd_new_lib_options& opt (t.lib_opt); - // Full package name vs base name (e.g., libhello in libhello.bash) vs the - // name stem (e.g, hello in libhello). - // - // We use the full name in the manifest and the top-level directory, the - // base name for inner filesystem directories and preprocessor macros, - // while the (sanitized) stem for modules, namespaces, etc. - // - const string& n (pkgn.string ()); // Full name. - const string& b (pkgn.base ()); // Base name. - const string& v (pkgn.variable ()); // Variable name. - string s (b); // Name stem. - { - // Warn about the lib prefix unless we are creating a source - // subdirectory, in which case the project is probably not meant to be a - // package anyway. + // In the source mode --output-dir|-o is the source subdirectory and so + // implies no source prefix. // - bool w (!o.subdirectory ()); + if (o.source () && o.output_dir_specified ()) + { + if (opt.split ()) + fail << "both --output-dir|-o and --type|-t,split specified"; - switch (t) + if (opt.prefix_specified ()) + fail << "both --output-dir|-o and --type|-t,prefix specified"; + + if (opt.prefix_include_specified ()) + fail << "both --output-dir|-o and --type|-t,prefix-include specified"; + + if (opt.prefix_source_specified ()) + fail << "both --output-dir|-o and --type|-t,prefix-source specified"; + } + + if (opt.split ()) { - case type::exe: - { - if (w && s.compare (0, 3, "lib") == 0) - warn << "executable name starts with 'lib'"; + if (opt.prefix_specified ()) + fail << "both --type|-t,split and --type|-t,prefix specified"; - break; - } - case type::lib: - { - if (s.compare (0, 3, "lib") == 0) - { - s.erase (0, 3); + if (opt.prefix_include_specified ()) + fail << "both --type|-t,split and --type|-t,prefix-include specified"; - if (w && s.empty ()) - fail << "empty library name stem in '" << b << "'"; - } - else if (w) - warn << "library name does not start with 'lib'"; + if (opt.prefix_source_specified ()) + fail << "both --type|-t,split and --type|-t,prefix-source specified"; - break; - } - case type::bare: - case type::empty: + pfx_inc = dir_path ("include"); + pfx_src = dir_path ("src"); break; } - } - // Sanitize the stem to be a valid language identifier. - // - string id; - switch (l) - { - case lang::c: - case lang::cxx: + if (opt.prefix_specified ()) { - id = sanitize_identifier (const_cast<const string&> (s)); + if (opt.prefix_include_specified ()) + fail << "both --type|-t,prefix and --type|-t,prefix-include specified"; + + if (opt.prefix_source_specified ()) + fail << "both --type|-t,prefix and --type|-t,prefix-source specified"; + + pfx_inc = pfx_src = opt.prefix (); break; } + + pfx_inc = opt.prefix_include (); + pfx_src = opt.prefix_source (); + break; } + case type::bare: + case type::empty: + break; + } - dir_path prj; // Project root directory. - dir_path out; // Project/package/subdirectory output directory. - optional<dir_path> pkg; // Package directory relative to its project root. - optional<dir_path> sub; // Source subdirectory relative to its - // project/package root. + // Note that in the --source mode, prj is the package (rather than the + // project) root if the subdirectory is created inside a package directory. + // + dir_path prj; // Project root directory. + optional<dir_path> pkg; // Package mode/directory relative to project root. + dir_path sub; // Source subdirectory relative to source prefix. + bool src (false); // Source subdirectory mode. + + dir_path out; // Project/package root output directory. + dir_path out_inc; // Include output directory. + dir_path out_src; // Source output directory. + + { + // In all the cases out_inc and our_src are derived the same way except + // for the --source mode if --output is specified. + // + auto set_out = [&sub, + &out, &out_inc, &out_src, + &pfx_inc, &pfx_src, + subdir, sub_inc, sub_src] + (const string& n) { - // Figure the final output and tentative project directories. - // - if (o.package ()) - { - if (o.directory_specified ()) - prj = normalize (o.directory (), "project"); - else - prj = current_directory (); + sub = (subdir != nullptr ? *subdir : + sub_inc ? dir_path (n) : // Note: no need to check for + dir_path ()); // sub_src (see above). - out = o.output_dir_specified () ? o.output_dir () : prj / dir_path (n); - normalize (out, "output"); - } - else if (o.subdirectory ()) - { - // In the subdirectory mode --output-dir|-o is the source subdirectory - // so for this mode we have two ways of specifying the same thing (but - // see also the output directory fallback above for a special case). - // - if (o.output_dir_specified () && source != nullptr) - fail << "both --output-dir|-o and --type|-t,source specified"; + out_inc = out / pfx_inc / (sub_inc ? sub : dir_path ()); + out_src = out / pfx_src / (sub_src ? sub : dir_path ()); + }; - if (o.directory_specified ()) - prj = normalize (o.directory (), "project"); - else - prj = current_directory (); + // Figure the final output and tentative project directories. + // + if (o.package ()) + { + if (o.directory_specified ()) + prj = normalize (o.directory (), "project"); + else + prj = current_directory (); - out = o.output_dir_specified () - ? o.output_dir () - : prj / (source != nullptr ? *source : dir_path (n)); + out = o.output_dir_specified () ? o.output_dir () : prj / dir_path (n); + normalize (out, "output"); + set_out (b); + } + else if (o.source ()) + { + // In the source mode --output-dir|-o is the source subdirectory so + // for this mode we have two ways of specifying the same thing (but + // see also the output directory fallback above for a special case). + // + if (o.output_dir_specified () && subdir != nullptr) + fail << "both --output-dir|-o and --type|-t,subdir specified"; - normalize (out, "output"); - } + if (o.directory_specified ()) + prj = normalize (o.directory (), "project"); else + prj = current_directory (); + + out = prj; + + if (o.output_dir_specified ()) { - out = o.output_dir_specified () ? o.output_dir () : dir_path (n); - normalize (out, "output"); - prj = out; + // Note that in this case we deffer determining the source + // subdirectory until the project directory is finalized. + // + out_src = o.output_dir (); + normalize (out_src, "output"); + out_inc = out_src; } + else + set_out (n); // Give user extra rope. + } + else + { + out = o.output_dir_specified () ? o.output_dir () : dir_path (n); + normalize (out, "output"); + prj = out; + set_out (b); + } - // Get the actual project/package information as "seen" from the output - // directory. - // - project_package pp ( - find_project_package (out, true /* ignore_not_found */)); + // Get the actual project/package information as "seen" from the output + // directory. + // + project_package pp ( + find_project_package (out, true /* ignore_not_found */)); - // Finalize the tentative project directory and do some sanity checks - // (nested packages, etc; you would be surprised what people come up - // with). - // - if (o.package ()) + // Finalize the tentative project directory and do some sanity checks + // (nested packages, etc; you would be surprised what people come up + // with). + // + if (o.package ()) + { + if (!o.no_checks ()) { - if (!o.no_checks ()) + if (pp.project.empty ()) + warn << prj << " does not look like a project directory"; + else { - if (pp.project.empty ()) - warn << prj << " does not look like a project directory"; - else - { - if (pp.package) - fail << "package directory " << out << " is inside another " - << "package directory " << pp.project / *pp.package << - info << "nested packages are not allowed"; - } + if (pp.package) + fail << "package directory " << out << " is inside another " + << "package directory " << pp.project / *pp.package << + info << "nested packages are not allowed"; } + } - if (!pp.project.empty ()) - { - if (prj != pp.project) - prj = move (pp.project); - } + if (!pp.project.empty ()) + { + if (prj != pp.project) + prj = move (pp.project); + } - if (!out.sub (prj)) - fail << "package directory " << out << " is not a subdirectory of " - << "project directory " << prj; + if (!out.sub (prj)) + fail << "package directory " << out << " is not a subdirectory of " + << "project directory " << prj; - pkg = out.leaf (prj); - } - else if (o.subdirectory ()) + pkg = out.leaf (prj); + } + else if (o.source ()) + { + if (!o.no_checks ()) { - if (!o.no_checks ()) - { - if (pp.project.empty () || !pp.package) - warn << prj << " does not look like a package directory"; - } + if (pp.project.empty () || !pp.package) + warn << prj << " does not look like a package directory"; + } - // Note: our prj should actually be the package (i.e., the build - // system project root). - // - if (!pp.project.empty ()) - { - dir_path pkg (move (pp.project)); + // Note: our prj should actually be the package (i.e., the build system + // project root). + // + if (!pp.project.empty ()) + { + dir_path pkg (move (pp.project)); - if (pp.package) - pkg /= *pp.package; + if (pp.package) + pkg /= *pp.package; - if (prj != pkg) - prj = move (pkg); - } + if (prj != pkg) + prj = move (pkg); + } - if (!out.sub (prj) || out == prj) - fail << "source subdirectory " << out << " is not a subdirectory of " - << "package directory " << prj; + // We use this information to form the include directories. The idea is + // that if the user places the subdirectory somewhere deeper (say into + // core/libfoo/), then we want the include directives to contain the + // prefix from the project root (so it will be <core/libfoo/...>) since + // all our buildfiles are hardwired with -I$src_root. + // + // Note also that a crafty user can adjust the prefix by picking the + // appropriate --directory|-d (i.e., it can point somewhere deeper than + // the project root). They will need to adjust their buildfiles, however + // (or we could get smarter by finding the actual package root and + // adding the difference to -I). Also, some other things, such as the + // namespace, currently do not contain the prefix. + // + if (o.output_dir_specified ()) // out_src == out_inc + { + if (!out_src.sub (prj) || out_src == prj) + fail << "source subdirectory " << out_src << " is not a " + << "subdirectory of package directory " << prj; - // We use this information to form the include directories. The idea - // is that if the user places the subdirectory somewhere deeper (say - // into core/libfoo/), then we want the include directives to contain - // the prefix from the project root (so it will be <core/libfoo/...>) - // since all our buildfiles are hardwired with -I$src_root. + // Here we treat out as subdirectory unless instructed otherwise in + // which case we treat it as a prefix. // - // Note also that a crafty user can adjust the prefix by picking the - // appropriate --directory|-d (i.e., it can point somewhere deeper - // than the project root). They will need to adjust their buildfiles, - // however (or we could get smarter by finding the actual package root - // and adding the difference to -I). Also, some other things, such as - // the namespace, currently do not contain the prefix. + // Note: no need to check for sub_src (see above). // - sub = out.leaf (prj); + dir_path s (out_src.leaf (prj)); + if (sub_inc) + sub = move (s); + else + pfx_inc = pfx_src = move (s); } - else + + src = true; + } + else + { + if (!o.no_checks ()) { - if (!o.no_checks ()) - { - if (!pp.project.empty ()) - fail << "project directory " << out << " is inside another " - << "project directory " << pp.project << - info << "nested projects are not allowed"; - } + if (!pp.project.empty ()) + fail << "project directory " << out << " is inside another " + << "project directory " << pp.project << + info << "nested projects are not allowed"; } - - // Create the output directory if it doesn't exit. Note that we are - // ok with it already existing and containing some things; see below - // for details. - // - if (!exists (out)) - mk_p (out); } + } - // Run pre/post hooks. - // - auto run_hooks = [&prj, &sub, &pkg, &n, &b, &s, &t, &l, &vc, &out] - (const strings& hooks, const char* what) - { - command_substitution_map subs; - strings vars; + // We cannot be creating a package and source subdirectory simultaneously + // and so the pkg and src cannot be both true. However, we can be creating a + // project in which case none of them are true. + // + assert ((!pkg && !src) || pkg.has_value () == !src); - auto add_var = [&subs, &vars] (string name, string value) - { - vars.push_back ("BDEP_NEW_" + - ucase (const_cast<const string&> (name)) + - '=' + - value); + // Note that the header and source directories may differ due to different + // source prefixes (--type|-t,prefix-{include,source}) as well as different + // source subdirectories (--type|-t,no-sub-source). + // + bool split (out_inc != out_src); - subs[move (name)] = move (value); - }; + // In the split mode allowing the header or source directories to be the + // project/package root directory could end up with clashing of the root, + // header, and/or source buildfiles in different combinations. For the sake + // of simplicity let's not support it for now. + // + if (split) + { + if (out_inc == out) + fail << "split header directory is project/package root"; - add_var ("mode", sub ? "subdirectory" : pkg ? "package" : "project"); - add_var ("name", n); - add_var ("base", b); - add_var ("stem", s); - add_var ("type", t.string ()); - add_var ("lang", l.string (true /* lower */)); - add_var ("vcs", vc.string ()); - add_var ("root", prj.string ()); + if (out_src == out) + fail << "split source directory is project/package root"; + } - // Note: out directory path is absolute and normalized. - // - optional<process_env> env (process_env (process_path (), out, vars)); + // Merging the source and root directory buildfiles is a bit hairy when the + // project has the functional/integration tests subproject. Thus, we require + // them to be explicitly disabled. Note however, that getting rid of this + // requirement is not too complicated and can be considered in the future. + // + if (t == type::lib && itest && out_src == out) + fail << "functional/integration testing is not supported in this layout" << + info << "specify --type|-t,no-tests explicitly"; - for (const string& cmd: hooks) - { - try - { - process_exit e (command_run (cmd, - env, - subs, - '@', - [] (const char* const args[], size_t n) - { - if (verb >= 2) - { - print_process (args, n); - } - })); + // Create the output directory if it doesn't exist. Note that we are ok with + // it already existing and containing some things; see below for details. + // + if (!exists (out)) + mk_p (out); - if (!e) - { - if (e.normal ()) - throw failed (); // Assume the command issued diagnostics. + // Run pre/post hooks. + // + auto run_hooks = [&prj, &src, &pkg, &n, &b, &s, &t, &l, &vc, + &out, &pfx_inc, &pfx_src, &sub] + (const strings& hooks, const char* what) + { + command_substitution_map subs; + strings vars; - fail << what << " hook '" << cmd << "' " << e; - } - } - catch (const invalid_argument& e) - { - fail << "invalid " << what << " hook '" << cmd << "': " << e; - } - catch (const io_error& e) - { - fail << "unable to execute " << what << " hook '" << cmd << "': " - << e; - } - // Also handles process_error exception (derived from system_error). - // - catch (const system_error& e) - { - fail << "unable to execute " << what << " hook '" << cmd << "': " - << e; - } - } + 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); }; - // Run pre hooks. - // - if (o.pre_hook_specified ()) - run_hooks (o.pre_hook (), "pre"); + add_var ("mode", src ? "source" : pkg ? "package" : "project"); + add_var ("name", n); + add_var ("base", b); + add_var ("stem", s); - // Source directory relative to package root. - // - const dir_path& d (sub ? *sub : source != nullptr ? *source : dir_path (b)); + if (pfx_inc != pfx_src) + { + add_var ("inc", pfx_inc.string ()); + add_var ("src", pfx_src.string ()); + } + else + add_var ("pfx", pfx_src.string ()); + + add_var ("sub", sub.string ()); + add_var ("type", t.string ()); + add_var ("lang", l.string (true /* lower */)); + add_var ("vcs", vc.string ()); + add_var ("root", prj.string ()); - // Check if certain things already exist. + // Note: out directory path is absolute and normalized. // - optional<vcs::vcs_type> vc_e; // Detected version control system. - optional<string> readme_e; // Extracted summary line. - optional<path> readme_f; // README file path. - optional<string> license_e; // Extracted license id. - optional<path> license_f; // LICENSE file path. - optional<path> copyright_f; // COPYRIGHT file path. - optional<path> authors_f; // AUTHORS file path. + optional<process_env> env (process_env (process_path (), out, vars)); + + for (const string& cmd: hooks) { - if (!sub) + try { - if (!pkg) + 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 (git_repository (out)) - vc_e = vcs::git; + if (e.normal ()) + throw failed (); // Assume the command issued diagnostics. + + fail << what << " hook '" << cmd << "' " << e; } + } + catch (const invalid_argument& e) + { + fail << "invalid " << what << " hook '" << cmd << "': " << e; + } + catch (const io_error& e) + { + fail << "unable to execute " << what << " hook '" << cmd << "': " << e; + } + // Also handles process_error exception (derived from system_error). + // + catch (const system_error& e) + { + fail << "unable to execute " << what << " hook '" << cmd << "': " << e; + } + } + }; - // @@ What if in the --package mode these files already exist but are - // not in the package but in the project root? This also goes back - // to an existing desire to somehow reuse project README.md/LICENSE - // in its packages (currently a reference from manifest out of - // package is illegal). Maybe via symlinks? We could probably even - // automatically find and symlink them? - // - path f; + // Run pre hooks. + // + if (o.pre_hook_specified ()) + run_hooks (o.pre_hook (), "pre"); - // README.md - // - if (exists ((f = out / "README.md"))) - { - readme_e = extract_summary (f, n); + // Check if certain things already exist. + // + optional<vcs::vcs_type> vc_e; // Detected version control system. + optional<string> readme_e; // Extracted summary line. + optional<path> readme_f; // README file path. + optional<string> license_e; // Extracted license id. + optional<path> license_f; // LICENSE file path. + optional<path> copyright_f; // COPYRIGHT file path. + optional<path> authors_f; // AUTHORS file path. + { + if (!src) + { + if (!pkg) + { + if (git_repository (out)) + vc_e = vcs::git; + } - if (readme_e->empty ()) - warn << "unable to extract project summary from " << f << - info << "using generic summary in manifest"; + // @@ What if in the --package mode these files already exist but are + // not in the package but in the project root? This also goes back to + // an existing desire to somehow reuse project README.md/LICENSE in + // its packages (currently a reference from manifest out of package + // is illegal). Maybe via symlinks? We could probably even + // automatically find and symlink them? + // + path f; - readme_f = move (f); - } + // README.md + // + if (exists ((f = out / "README.md"))) + { + readme_e = extract_summary (f, n); - // LICENSE or UNLICENSE - // - if (exists ((f = out / "LICENSE")) || exists ((f = out / "UNLICENSE"))) - { - license_e = extract_license (f); + if (readme_e->empty ()) + warn << "unable to extract project summary from " << f << + info << "using generic summary in manifest"; - if (license_e->empty () && !license_o) - fail << "unable to guess project license from " << f << - info << "use --type|-t,license sub-option to specify explicitly"; + readme_f = move (f); + } - license_f = move (f); - } + // LICENSE or UNLICENSE + // + if (exists ((f = out / "LICENSE")) || exists ((f = out / "UNLICENSE"))) + { + license_e = extract_license (f); - // COPYRIGHT - // - if (exists ((f = out / "COPYRIGHT"))) - { - copyright_f = move (f); - } + if (license_e->empty () && !license_o) + fail << "unable to guess project license from " << f << + info << "use --type|-t,license sub-option to specify explicitly"; - // AUTHORS - // - // Note that some projects distinguish between AUTHORS (legal names - // for copyright purposes) and CONTRIBUTORS (individuals that have - // contribute and/or are allowed to contribute to the project). It's - // not clear whether we should distribute/install CONTRIBUTORS. - // - if (exists ((f = out / "AUTHORS"))) - { - authors_f = move (f); - } + license_f = move (f); } - // Merge option and existing values verifying that what already exists - // does not conflict with what's requested. + // COPYRIGHT // - if (vc_e) + if (exists ((f = out / "COPYRIGHT"))) { - if (!vc_o) - vc = *vc_e; - else if (*vc_e != vc) - fail << "existing version control system does not match requested" << - info << "existing: " << *vc_e << - info << "requested: " << vc; + copyright_f = move (f); } - if (readme_f) + // AUTHORS + // + // Note that some projects distinguish between AUTHORS (legal names for + // copyright purposes) and CONTRIBUTORS (individuals that have + // contribute and/or are allowed to contribute to the project). It's not + // clear whether we should distribute/install CONTRIBUTORS. + // + if (exists ((f = out / "AUTHORS"))) { - if (!readme) - fail << "--type|-t,no-readme sub-option specified but README " - << "already exists"; + authors_f = move (f); } + } - if (license_e) + // Merge option and existing values verifying that what already exists + // does not conflict with what's requested. + // + if (vc_e) + { + if (!vc_o) + vc = *vc_e; + else if (*vc_e != vc) + fail << "existing version control system does not match requested" << + info << "existing: " << *vc_e << + info << "requested: " << vc; + } + + if (readme_f) + { + if (!readme) + fail << "--type|-t,no-readme sub-option specified but README " + << "already exists"; + } + + if (license_e) + { + if (!license_o) { - if (!license_o) - { - // We should have failed earlier if the license wasn't recognized. - // - assert (!license_e->empty ()); + // We should have failed earlier if the license wasn't recognized. + // + assert (!license_e->empty ()); - license = *license_e; - } - else if (!license_e->empty () && icasecmp (*license_e, license) != 0) - fail << "extracted license does not match requested" << - info << "extracted: " << *license_e << - info << "requested: " << license; + license = *license_e; } + else if (!license_e->empty () && icasecmp (*license_e, license) != 0) + fail << "extracted license does not match requested" << + info << "extracted: " << *license_e << + info << "requested: " << license; } + } - // Initialize the version control system. Do it before writing anything - // ourselves in case it fails. Also, the email discovery may do the VCS - // detection. + // Initialize the version control system. Do it before writing anything + // ourselves in case it fails. Also, the email discovery may do the VCS + // detection. + // + if (!vc_e && !pkg && !src) + { + switch (vc) + { + case vcs::git: run ("git", "init", "-q", out); break; + case vcs::none: break; + } + } + + // We support creating a project that already contains some files provided + // none of them conflict with what we are trying to create (with a few + // exceptions such as LICENSE and README.md that are handled explicitly plus + // packages.manifest to which we append last). + // + // While we could verify at the outset that none of the files we will be + // creating exist, that would be quite unwieldy. So instead we are going to + // fail as we go along but, in this case, also cleanup any files that we + // have already created. + // + vector<auto_rmfile> rms; + for (path cf;;) // Breakout loop with the current file being written. + try + { + ofdstream os; + auto open = [&cf, &os, &rms] (path f) + { + mk_p (f.directory ()); + + try + { + os.open (f, (fdopen_mode::out | + fdopen_mode::create | + fdopen_mode::exclusive)); + cf = f; + rms.push_back (auto_rmfile (move (f))); + } + catch (const io_error& e) + { + fail << "unable to create " << f << ": " << e; + } + }; + + // .gitignore & .gitattributes // - if (!vc_e && !pkg && !sub) + // Write the root .gitignore file content to the stream, optionally adding + // an additional newline at the end. + // + auto write_root_gitignore = [&os, &pkg, t] (bool newline = false) { - switch (vc) + if (!pkg) + os << bdep_dir.posix_representation () << '\n' + << '\n' + << "# Local default options files." << '\n' + << "#" << '\n' + << ".build2/local/" << '\n'; + if (t != type::empty) + { + if (!pkg) + os << '\n'; + os << "# Compiler/linker output." << '\n' + << "#" << '\n' + << "*.d" << '\n' + << "*.t" << '\n' + << "*.i" << '\n' + << "*.ii" << '\n' + << "*.o" << '\n' + << "*.obj" << '\n' + << "*.so" << '\n' + << "*.dll" << '\n' + << "*.a" << '\n' + << "*.lib" << '\n' + << "*.exp" << '\n' + << "*.pdb" << '\n' + << "*.ilk" << '\n' + << "*.exe" << '\n' + << "*.exe.dlls/" << '\n' + << "*.exe.manifest" << '\n' + << "*.pc" << '\n'; + } + + // Only print the newline if anything is printed. + // + if (newline && (!pkg || t != type::empty)) + os << '\n'; + }; + + // See also tests/.gitignore below. + // + if (vc == vcs::git) + { + if (!src && out != out_src) + { + // Note: use POSIX directory separators in these files. + // + open (out / ".gitignore"); + write_root_gitignore (); + os.close (); + } + + if (!pkg && !src) { - case vcs::git: run ("git", "init", "-q", out); break; - case vcs::none: break; + open (out / ".gitattributes"); + os << "# This is a good default: files that are auto-detected by git to be text are" << '\n' + << "# converted to the platform-native line ending (LF on Unix, CRLF on Windows)" << '\n' + << "# in the working tree and to LF in the repository." << '\n' + << "#" << '\n' + << "* text=auto" << '\n' + << '\n' + << "# Use `eol=crlf` for files that should have the CRLF line ending both in the" << '\n' + << "# working tree (even on Unix) and in the repository." << '\n' + << "#" << '\n' + << "#*.bat text eol=crlf" << '\n' + << '\n' + << "# Use `eol=lf` for files that should have the LF line ending both in the" << '\n' + << "# working tree (even on Windows) and in the repository." << '\n' + << "#" << '\n' + << "#*.sh text eol=lf" << '\n' + << '\n' + << "# Use `binary` to make sure certain files are never auto-detected as text." << '\n' + << "#" << '\n' + << "#*.png binary" << '\n'; + os.close (); } } - // We support creating a project that already contains some files provided - // none of them conflict with what we are trying to create (with a few - // exceptions such as LICENSE and README.md that are handled explicitly - // plus packages.manifest to which we append last). + // repositories.manifest // - // While we could verify at the outset that none of the files we will be - // creating exist, that would be quite unwieldy. So instead we are going - // to fail as we go along but, in this case, also cleanup any files that - // we have already created. + if (!pkg && !src) + { + open (out / "repositories.manifest"); + os << ": 1" << '\n' + << "summary: " << n << " project repository" << '\n' + << '\n' + << "#:" << '\n' + << "#role: prerequisite" << '\n' + << "#location: https://pkg.cppget.org/1/stable" << '\n' + << "#trust: ..." << '\n' + << '\n' + << "#:" << '\n' + << "#role: prerequisite" << '\n' + << "#location: https://git.build2.org/hello/libhello.git" << '\n'; + os.close (); + } + + // README.md // - vector<auto_rmfile> rms; - for (path cf;;) // Breakout loop with the current file being written. - try + if (!readme_f && readme) { - ofdstream os; - auto open = [&cf, &os, &rms] (path f) + open (*(readme_f = out / "README.md")); + switch (t) { - try + case type::exe: + case type::lib: { - os.open (f, (fdopen_mode::out | - fdopen_mode::create | - fdopen_mode::exclusive)); - cf = f; - rms.push_back (auto_rmfile (move (f))); + // @@ Maybe we should generate a "Hello, World" description and + // usage example as a guide, at least for a library? + + os << "# " << n << '\n' + << '\n' + << l << " " << t << '\n'; + break; } - catch (const io_error& e) + case type::bare: + case type::empty: { - fail << "unable to create " << f << ": " << e; + os << "# " << n << '\n'; + break; } - }; + } + os.close (); + } + + if (t == type::empty) + break; // Done. - // .gitignore & .gitattributes + // manifest + // + if (!src) + { + // Project name. // - // See also tests/.gitignore below. + // If this is a package in a project (--package mode), then use the + // project directory name as the project name. Otherwise, the project + // name is the same as the package and is therefore omitted. // - if (vc == vcs::git) + // In case of a library, we could have used either the full name or the + // stem without the lib prefix. And it could go either way: if a library + // is (likely to be) accompanied by an executable (or some other extra + // packages), then its project should probably be the stem. Otherwise, + // if it is a standalone library, then the full library name is probably + // preferred. The stem also has another problem: it could be an invalid + // project name. So using the full name seems like a simpler and more + // robust approach. + // + // There was also an idea to warn if the project name ends with a digit + // (think libfoo and libfoo2). + // + optional<project_name> pn; + if (pkg) { - if (!sub) - { - // Note: use POSIX directory separators in these files. - // - open (out / ".gitignore"); - if (!pkg) - os << bdep_dir.posix_representation () << endl - << endl - << "# Local default options files." << endl - << "#" << endl - << ".build2/local/" << endl - << endl; - if (t != type::empty) - os << "# Compiler/linker output." << endl - << "#" << endl - << "*.d" << endl - << "*.t" << endl - << "*.i" << endl - << "*.ii" << endl - << "*.o" << endl - << "*.obj" << endl - << "*.so" << endl - << "*.dll" << endl - << "*.a" << endl - << "*.lib" << endl - << "*.exp" << endl - << "*.pdb" << endl - << "*.ilk" << endl - << "*.exe" << endl - << "*.exe.dlls/" << endl - << "*.exe.manifest" << endl - << "*.pc" << endl; - os.close (); - } + string p (prj.leaf ().string ()); - if (!pkg && !sub) + if (p != n) // Omit if the same as the package name. { - open (out / ".gitattributes"); - os << "# This is a good default: files that are auto-detected by git to be text are" << endl - << "# converted to the platform-native line ending (LF on Unix, CRLF on Windows)" << endl - << "# in the working tree and to LF in the repository." << endl - << "#" << endl - << "* text=auto" << endl - << endl - << "# Use `eol=crlf` for files that should have the CRLF line ending both in the" << endl - << "# working tree (even on Unix) and in the repository." << endl - << "#" << endl - << "#*.bat text eol=crlf" << endl - << endl - << "# Use `eol=lf` for files that should have the LF line ending both in the" << endl - << "# working tree (even on Windows) and in the repository." << endl - << "#" << endl - << "#*.sh text eol=lf" << endl - << endl - << "# Use `binary` to make sure certain files are never auto-detected as text." << endl - << "#" << endl - << "#*.png binary" << endl; - os.close (); + try + { + pn = project_name (move (p)); + } + catch (const invalid_argument& e) + { + warn << "project name '" << p << "' is invalid: " << e << + info << "leaving the 'project' manifest value empty"; + + pn = project_name (); + } } } - // repositories.manifest + // Project email. // - if (!pkg && !sub) + string pe; { - open (out / "repositories.manifest"); - os << ": 1" << endl - << "summary: " << n << " project repository" << endl - << endl - << "#:" << endl - << "#role: prerequisite" << endl - << "#location: https://pkg.cppget.org/1/stable" << endl - << "#trust: ..." << endl - << endl - << "#:" << endl - << "#role: prerequisite" << endl - << "#location: https://git.build2.org/hello/libhello.git" << endl; - os.close (); + optional<string> r (find_project_author_email (prj)); + pe = r ? move (*r) : "you@example.org"; } - // README.md + // Full license name. // - if (!readme_f && readme) + string ln; { - open (*(readme_f = out / "README.md")); - switch (t) + auto i (licenses.find (license)); + if (i != licenses.end ()) { - case type::exe: - case type::lib: - { - // @@ Maybe we should generate a "Hello, World" description and - // usage example as a guide, at least for a library? - - os << "# " << n << endl - << endl - << l << " " << t << endl; - break; - } - case type::bare: - case type::empty: - { - os << "# " << n << endl; - break; - } + ln = i->second; + license = i->first; // Use canonical case. } - os.close (); } - if (t == type::empty) - break; // Done. + open (out / "manifest"); + os << ": 1" << '\n' + << "name: " << n << '\n' + << "version: 0.1.0-a.0.z" << '\n'; + if (pn) + os << "project: " << *pn << '\n'; + if (readme_e && !readme_e->empty ()) + os << "summary: " << *readme_e << '\n'; + else + os << "summary: " << s << " " << l << " " << t << '\n'; + if (ln.empty ()) + os << "license: " << license << '\n'; + else + os << "license: " << license << " ; " << ln << "." << '\n'; + if (readme_f) + os << "description-file: " << readme_f->leaf (out).posix_representation () << '\n'; + os << "url: https://example.org/" << (pn ? pn->string () : n) << '\n' + << "email: " << pe << '\n' + << "depends: * build2 >= 0.12.0" << '\n' + << "depends: * bpkg >= 0.12.0" << '\n' + << "#depends: libhello ^1.0.0" << '\n'; + os.close (); + } - // manifest - // - if (!sub) + string m; // Language module. + string x; // Source target type. + string h; // Header target type. + string hs; // All header-like target types. + string xe; // Source file extension (including leading dot). + string he; // Header file extension (including leading dot unless empty). + + // @@ In a modular project, mxx is probably more like hxx/cxx rather + // than ixx/txx. + // + optional<string> ie; // Inline file extension. + optional<string> te; // Template file extension. + optional<string> me; // Module interface extension. + + switch (l) + { + case lang::c: { - // Project name. - // - // If this is a package in a project (--package mode), then use the - // project directory name as the project name. Otherwise, the project - // name is the same as the package and is therefore omitted. - // - // In case of a library, we could have used either the full name or - // the stem without the lib prefix. And it could go either way: if a - // library is (likely to be) accompanied by an executable (or some - // other extra packages), then its project should probably be the - // stem. Otherwise, if it is a standalone library, then the full - // library name is probably preferred. The stem also has another - // problem: it could be an invalid project name. So using the full - // name seems like a simpler and more robust approach. - // - // There was also an idea to warn if the project name ends with a - // digit (think libfoo and libfoo2). + m = "c"; + x = "c"; + h = "h"; + hs = "h"; + xe = ".c"; + he = ".h"; + break; + } + case lang::cxx: + { + const auto& opt (l.cxx_opt); + + m = "cxx"; + x = "cxx"; + h = "hxx"; + hs = "hxx"; + + // Return the extension (v), if specified (s), derive the extension + // from the pattern and type (t), or return the default (d), if + // specified. // - optional<project_name> pn; - if (pkg) + auto ext = [&opt] (bool s, + const string& v, + optional<char> t, + const char* d = nullptr) -> optional<string> { - string p (prj.leaf ().string ()); + optional<string> r; - if (p != n) // Omit if the same as the package name. + if (s) + r = v; + else if (t && (opt.extension_specified () || opt.cpp ())) { - try - { - pn = project_name (move (p)); - } - catch (const invalid_argument& e) - { - warn << "project name '" << p << "' is invalid: " << e << - info << "leaving the 'project' manifest value empty"; + string p (opt.extension_specified () ? opt.extension () : + opt.cpp () ? "?pp" : ""); - pn = project_name (); - } + replace (p.begin (), p.end (), '?', *t); + r = move (p); } - } + else if (d != nullptr) + r = d; - // Project email. + // Add leading dot if absent. + // + if (r && !r->empty () && r->front () != '.') + r = '.' + *r; + + return r; + }; + + xe = *ext (opt.cxx_specified (), opt.cxx (), 'c', "cxx"); + he = *ext (opt.hxx_specified (), opt.hxx (), 'h', "hxx"); + + // We only want default .ixx/.txx/.mxx if the user didn't specify any + // of the extension-related options explicitly. // - string pe; - { - optional<string> r (find_project_author_email (prj)); - pe = r ? move (*r) : "you@example.org"; - } + bool d (!opt.cxx_specified () && + !opt.hxx_specified () && + !opt.ixx_specified () && + !opt.txx_specified () && + !opt.mxx_specified ()); + + ie = ext (opt.ixx_specified (), opt.ixx (), 'i', d ? "ixx" : nullptr); + te = ext (opt.txx_specified (), opt.txx (), 't', d ? "txx" : nullptr); - // Full license name. + // For now only include mxx in buildfiles if its extension was + // explicitly specified with mxx=. // - string ln; - { - auto i (licenses.find (license)); - if (i != licenses.end ()) - { - ln = i->second; - license = i->first; // Use canonical case. - } - } + me = ext (opt.mxx_specified (), opt.mxx (), nullopt, nullptr); - open (out / "manifest"); - os << ": 1" << endl - << "name: " << n << endl - << "version: 0.1.0-a.0.z" << endl; - if (pn) - os << "project: " << *pn << endl; - if (readme_e && !readme_e->empty ()) - os << "summary: " << *readme_e << endl; - else - os << "summary: " << s << " " << l << " " << t << endl; - if (ln.empty ()) - os << "license: " << license << endl; - else - os << "license: " << license << " ; " << ln << "." << endl; - if (readme_f) - os << "description-file: " << readme_f->leaf (out).posix_representation () << endl; - os << "url: https://example.org/" << (pn ? pn->string () : n) << endl - << "email: " << pe << endl - << "depends: * build2 >= 0.12.0" << endl - << "depends: * bpkg >= 0.12.0" << endl - << "#depends: libhello ^1.0.0" << endl; - os.close (); + if (ie) hs += " ixx"; + if (te) hs += " txx"; + if (me) hs += " mxx"; + + break; } + } - string m; // Language module. - string x; // Source target type. - string h; // Header target type. - string hs; // All header-like target types. - string xe; // Source file extension (including leading dot). - string he; // Header file extension (including leading dot unless empty). + // Return the pointer to the extension suffix after the leading dot or to + // the extension beginning if it is empty. + // + auto pure_ext = [] (const string& e) + { + assert (e.empty () || e[0] == '.'); + return e.c_str () + (e.empty () ? 0 : 1); + }; - // @@ In a modular project, mxx is probably more like hxx/cxx rather - // than ixx/txx. + // build/ + // + dir_path bd; + if (!src) + { + bd = out / build_dir; + + // build/bootstrap.build + // + open (bd / "bootstrap." + build_ext); + os << "project = " << n << '\n'; + if (o.no_amalgamation ()) + os << "amalgamation = # Disabled." << '\n'; + os << '\n' + << "using version" << '\n' + << "using config" << '\n'; + if (itest || utest) + os << "using test" << '\n'; + if (install) + os << "using install" << '\n'; + os << "using dist" << '\n'; + os.close (); + + // build/root.build + // + // Note: see also tests/build/root.build below. // - optional<string> ie; // Inline file extension. - optional<string> te; // Template file extension. - optional<string> me; // Module interface extension. + open (bd / "root." + build_ext); switch (l) { case lang::c: { - m = "c"; - x = "c"; - h = "h"; - hs = "h"; - xe = ".c"; - he = ".h"; + // @@ TODO: 'latest' in c.std. + // + // << "c.std = latest" << '\n' + // << '\n' + os << "using c" << '\n' + << '\n' + << "h{*}: extension = h" << '\n' + << "c{*}: extension = c" << '\n'; break; } case lang::cxx: { - const auto& opt (l.cxx_opt); - - m = "cxx"; - x = "cxx"; - h = "hxx"; - hs = "hxx"; + os << "cxx.std = latest" << '\n' + << '\n' + << "using cxx" << '\n' + << '\n'; - // Return the extension (v), if specified (s), derive the extension - // from the pattern and type (t), or return the default (d), if - // specified. - // - auto ext = [&opt] (bool s, - const string& v, - optional<char> t, - const char* d = nullptr) -> optional<string> - { - optional<string> r; + if (me) os << "mxx{*}: extension = " << pure_ext (*me) << '\n'; + os << "hxx{*}: extension = " << pure_ext (he) << '\n'; + if (ie) os << "ixx{*}: extension = " << pure_ext (*ie) << '\n'; + if (te) os << "txx{*}: extension = " << pure_ext (*te) << '\n'; + os << "cxx{*}: extension = " << pure_ext (xe) << '\n'; - if (s) - r = v; - else if (t && (opt.extension_specified () || opt.cpp ())) - { - string p (opt.extension_specified () ? opt.extension () : - opt.cpp () ? "?pp" : ""); + break; + } + } - replace (p.begin (), p.end (), '?', *t); - r = move (p); - } - else if (d != nullptr) - r = d; + if ((itest || utest) && !m.empty ()) + os << '\n' + << "# The test target for cross-testing (running tests under Wine, etc)." << '\n' + << "#" << '\n' + << "test.target = $" << m << ".target" << '\n'; - // Add leading dot if absent. - // - if (r && !r->empty () && r->front () != '.') - r = '.' + *r; + os.close (); - return r; - }; + // build/.gitignore + // + if (vc == vcs::git) + { + open (bd / ".gitignore"); + os << "/config." << build_ext << '\n' + << "/root/" << '\n' + << "/bootstrap/" << '\n' + << "build/" << '\n'; + os.close (); + } + } - xe = *ext (opt.cxx_specified (), opt.cxx (), 'c', "cxx"); - he = *ext (opt.hxx_specified (), opt.hxx (), 'h', "hxx"); + // buildfile + // + // Write the root directory doc type prerequisites to the stream, + // optionally adding the trailing newline. + // + auto write_doc_prerequisites = [ + &os, &out, + &readme_f, &license_f, ©right_f, &authors_f] (bool newline = false) + { +#if 1 // @@ TMP + if (readme_f || license_f || copyright_f || authors_f) + { + auto write = [&os, &out, s = ""] (const path& f) mutable + { + os << s << f.leaf (out).posix_representation (); + s = " "; + }; - // We only want default .ixx/.txx/.mxx if the user didn't specify - // any of the extension-related options explicitly. - // - bool d (!opt.cxx_specified () && - !opt.hxx_specified () && - !opt.ixx_specified () && - !opt.txx_specified () && - !opt.mxx_specified ()); + os << "doc{"; + if (readme_f) write (*readme_f); + if (license_f) write (*license_f); + if (copyright_f) write (*copyright_f); + if (authors_f) write (*authors_f); + os << "} "; + } +#else + if (readme_f || license_f || copyright_f || authors_f) + { + const char* s; + auto write = [&os, &out, &s] (const path& f) + { + os << s << f.leaf (out).posix_representation (); + s = " "; + }; - ie = ext (opt.ixx_specified (), opt.ixx (), 'i', d ? "ixx" : nullptr); - te = ext (opt.txx_specified (), opt.txx (), 't', d ? "txx" : nullptr); + if (readme_f) + { + s = ""; - // For now only include mxx in buildfiles if its extension was - // explicitly specified with mxx=. - // - me = ext (opt.mxx_specified (), opt.mxx (), nullopt, nullptr); + os << "doc{"; + write (*readme_f); + os << "} "; + } - if (ie) hs += " ixx"; - if (te) hs += " txx"; - if (me) hs += " mxx"; + if (license_f || copyright_f || authors_f) + { + s = ""; - break; + os << "legal{"; + if (license_f) write (*license_f); + if (copyright_f) write (*copyright_f); + if (authors_f) write (*authors_f); + os << "} "; } } +#endif + os << "manifest"; - // Return the pointer to the extension suffix after the leading dot or - // to the extension beginning if it is empty. - // - auto pure_ext = [] (const string& e) - { - assert (e.empty () || e[0] == '.'); - return e.c_str () + (e.empty () ? 0 : 1); - }; + if (newline) + os << '\n'; + }; - // build/ - // - dir_path bd; - if (!sub) - { - bd = out / build_dir; - mk (bd); + if (!src && out != out_src) + { + open (out / buildfile_file); - // build/bootstrap.build - // - open (bd / "bootstrap." + build_ext); - os << "project = " << n << endl; - if (o.no_amalgamation ()) - os << "amalgamation = # Disabled." << endl; - os << endl - << "using version" << endl - << "using config" << endl; - if (itest || utest) - os << "using test" << endl; - if (install) - os << "using install" << endl; - os << "using dist" << endl; - os.close (); + os << "./: {*/ -" << build_dir.posix_representation () << "} "; + write_doc_prerequisites (true /* newline */); - // build/root.build - // - // Note: see also tests/build/root.build below. - // - open (bd / "root." + build_ext); + if (itest && install && t == type::lib) // Have tests/ subproject. + os << '\n' + << "# Don't install tests." << '\n' + << "#" << '\n' + << "tests/: install = false" << '\n'; + os.close (); + } + if (t == type::bare) + break; // Done + + switch (t) + { + case type::exe: + { switch (l) { case lang::c: { - // @@ TODO: 'latest' in c.std. + // <src>/<stem>.c // - // << "c.std = latest" << endl - // << endl - os << "using c" << endl - << endl - << "h{*}: extension = h" << endl - << "c{*}: extension = c" << endl; + open (out_src / s + ".c"); + os << "#include <stdio.h>" << '\n' + << '\n' + << "int main (int argc, char *argv[])" << '\n' + << "{" << '\n' + << " if (argc < 2)" << '\n' + << " {" << '\n' + << " fprintf (stderr, \"error: missing name\\n\");" << '\n' + << " return 1;" << '\n' + << " }" << '\n' + << '\n' + << " printf (\"Hello, %s!\\n\", argv[1]);" << '\n' + << " return 0;" << '\n' + << "}" << '\n'; + os.close (); + break; } case lang::cxx: { - os << "cxx.std = latest" << endl - << endl - << "using cxx" << endl - << endl; - - if (me) os << "mxx{*}: extension = " << pure_ext (*me) << endl; - os << "hxx{*}: extension = " << pure_ext (he) << endl; - if (ie) os << "ixx{*}: extension = " << pure_ext (*ie) << endl; - if (te) os << "txx{*}: extension = " << pure_ext (*te) << endl; - os << "cxx{*}: extension = " << pure_ext (xe) << endl; + // <src>/<stem>.<cxx-ext> + // + open (out_src / s + xe); + os << "#include <iostream>" << '\n' + << '\n' + << "int main (int argc, char* argv[])" << '\n' + << "{" << '\n' + << " using namespace std;" << '\n' + << '\n' + << " if (argc < 2)" << '\n' + << " {" << '\n' + << " cerr << \"error: missing name\" << endl;" << '\n' + << " return 1;" << '\n' + << " }" << '\n' + << '\n' + << " cout << \"Hello, \" << argv[1] << '!' << endl;" << '\n' + << "}" << '\n'; + os.close (); break; } } - if ((itest || utest) && !m.empty ()) - os << endl - << "# The test target for cross-testing (running tests under Wine, etc)." << endl - << "#" << endl - << "test.target = $" << m << ".target" << endl; - - os.close (); + // <src>/buildfile + // + open (out_src / buildfile_file); + os << "libs =" << '\n' + << "#import libs += libhello%lib{hello}" << '\n' + << '\n'; - // build/.gitignore + // Add the root buildfile content, if required. // - if (vc == vcs::git) + if (out_src == out) { - open (bd / ".gitignore"); - os << "/config." << build_ext << endl - << "/root/" << endl - << "/bootstrap/" << endl - << "build/" << endl; - os.close (); + os << "./: exe{" << s << "} "; + write_doc_prerequisites (true /* newline */); + os << '\n'; } - } - // buildfile - // - if (!sub) - { - open (out / buildfile_file); - - os << "./: {*/ -" << build_dir.posix_representation () << "} "; -#if 1 // @@ TMP - if (readme_f || license_f || copyright_f || authors_f) + if (!utest) + os << "exe{" << s << "}: " << + "{" << hs << ' ' << x << "}{**} " << + "$libs" << + (itest ? " testscript" : "") << '\n'; + else { - auto write = [&os, &out, s = ""] (const path& f) mutable - { - os << s << f.leaf (out).posix_representation (); - s = " "; - }; + os << "./: exe{" << s << "}: libue{" << s << "}: " << + "{" << hs << ' ' << x << "}{** -**.test...} $libs" << '\n'; - os << "doc{"; - if (readme_f) write (*readme_f); - if (license_f) write (*license_f); - if (copyright_f) write (*copyright_f); - if (authors_f) write (*authors_f); - os << "} "; + if (itest) + os << "exe{" << s << "}: testscript" << '\n'; + + os << '\n' + << "# Unit tests." << '\n' + << "#" << '\n'; + + if (install) + os << "exe{*.test}:" << '\n' + << "{" << '\n' + << " test = true" << '\n' + << " install = false" << '\n' + << "}" << '\n'; + else + os << "exe{*.test}: test = true" << '\n'; + + os << '\n' + << "for t: " << x << "{**.test...}" << '\n' + << "{" << '\n' + << " d = $directory($t)" << '\n' + << " n = $name($t)..." << '\n' + << '\n' + << " ./: $d/exe{$n}: $t $d/{" << hs << + "}{+$n} $d/testscript{+$n}" << '\n' + << " $d/exe{$n}: libue{" << s << "}: bin.whole = false" << '\n' + << "}" << '\n'; } -#else - if (readme_f || license_f || copyright_f || authors_f) - { - const char* s; - auto write = [&os, &out, &s] (const path& f) - { - os << s << f.leaf (out).posix_representation (); - s = " "; - }; - if (readme_f) - { - s = ""; + string ps (pfx_src.posix_representation ()); - os << "doc{"; - write (*readme_f); - os << "} "; - } + string op (!ps.empty () ? "$out_pfx" : "$out_root"); + string sp (!ps.empty () ? "$src_pfx" : "$src_root"); - if (license_f || copyright_f || authors_f) - { - s = ""; + if (!ps.empty ()) + os << '\n' + << "out_pfx = [dir_path] $out_root/" << ps << '\n' + << "src_pfx = [dir_path] $src_root/" << ps << '\n'; - os << "legal{"; - if (license_f) write (*license_f); - if (copyright_f) write (*copyright_f); - if (authors_f) write (*authors_f); - os << "} "; - } - } -#endif - os << "manifest" << endl; + os << '\n' + << m << ".poptions =+ \"-I" << op << "\" \"-I" << sp << '"' << '\n'; - if (itest && install && t == type::lib) // Have tests/ subproject. - os << endl - << "# Don't install tests." << endl - << "#" << endl - << "tests/: install = false" << endl; os.close (); - } - if (t == type::bare) - break; // Done + // <src>/.gitignore + // + if (vc == vcs::git) + { + open (out_src / ".gitignore"); - // <base>/ (source subdirectory, can be overriden). - // - const dir_path& sd (sub ? out : out / d); - mk_p (sd); + // Add the root .gitignore file content, if required. + // + if (out_src == out) + write_root_gitignore (true /* newline */); - switch (t) - { - case type::exe: + os << s << '\n'; + if (utest) + os << "*.test" << '\n'; + if (itest || utest) + os << '\n' + << "# Testscript output directory (can be symlink)." << '\n' + << "#" << '\n'; + if (itest) + os << "test-" << s << '\n'; + if (utest) + os << "test-*.test" << '\n'; + os.close (); + } + + // <src>/testscript + // + if (itest) + { + open (out_src / "testscript"); + os << ": basics" << '\n' + << ":" << '\n' + << "$* 'World' >'Hello, World!'" << '\n' + << '\n' + << ": missing-name" << '\n' + << ":" << '\n' + << "$* 2>>EOE != 0" << '\n' + << "error: missing name" << '\n' + << "EOE" << '\n'; + os.close (); + } + + // <src>/<stem>.test.* + // + if (utest) { switch (l) { case lang::c: { - // <base>/<stem>.c + // <src>/<stem>.test.c // - open (sd / s + ".c"); - os << "#include <stdio.h>" << endl - << endl - << "int main (int argc, char *argv[])" << endl - << "{" << endl - << " if (argc < 2)" << endl - << " {" << endl - << " fprintf (stderr, \"error: missing name\\n\");"<< endl - << " return 1;" << endl - << " }" << endl - << endl - << " printf (\"Hello, %s!\\n\", argv[1]);" << endl - << " return 0;" << endl - << "}" << endl; + open (out_src / s + ".test.c"); + os << "#include <stdio.h>" << '\n' + << "#include <assert.h>" << '\n' + << '\n' + << "int main ()" << '\n' + << "{" << '\n' + << " return 0;" << '\n' + << "}" << '\n'; os.close (); break; } case lang::cxx: { - // <base>/<stem>.<cxx-ext> + // <src>/<stem>.test.<cxx-ext> // - open (sd / s + xe); - os << "#include <iostream>" << endl - << endl - << "int main (int argc, char* argv[])" << endl - << "{" << endl - << " using namespace std;" << endl - << endl - << " if (argc < 2)" << endl - << " {" << endl - << " cerr << \"error: missing name\" << endl;" << endl - << " return 1;" << endl - << " }" << endl - << endl - << " cout << \"Hello, \" << argv[1] << '!' << endl;" << endl - << "}" << endl; + open (out_src / s + ".test" + xe); + os << "#include <cassert>" << '\n' + << "#include <iostream>" << '\n' + << '\n' + << "int main ()" << '\n' + << "{" << '\n' + << '\n' + << "}" << '\n'; os.close (); break; } } + } - // <base>/buildfile - // - open (sd / buildfile_file); - os << "libs =" << endl - << "#import libs += libhello%lib{hello}" << endl - << endl; + break; + } + case type::lib: + { + // Include prefix. + // + // Note: if sub is not empty, then there is no need to check if + // sub_inc is true (see above). + // + string ip (sub.posix_representation ()); - if (!utest) - os << "exe{" << s << "}: " << - "{" << hs << ' ' << x << "}{**} " << - "$libs" << - (itest ? " testscript" : "") << endl; - else - { - os << "./: exe{" << s << "}: libue{" << s << "}: " << - "{" << hs << ' ' << x << "}{** -**.test...} $libs" << endl; + // Macro prefix. + // + // In absence of the source subdirectory, fallback to the package name + // to minimize the potential macro name clash. + // + string mp ( + sanitize_identifier ( + ucase (const_cast<const string&> (!ip.empty () ? ip : n)))); - if (itest) - os << "exe{" << s << "}: testscript" << endl; + // Strip the trailing underscore (produced from slash). + // + if (!ip.empty ()) + mp.pop_back (); - os << endl - << "# Unit tests." << endl - << "#" << endl; + string apih; // API header name. + string exph; // Export header name (empty if binless). + string verh; // Version header name. - if (install) - os << "exe{*.test}:" << endl - << "{" << endl - << " test = true" << endl - << " install = false" << endl - << "}" << endl; - else - os << "exe{*.test}: test = true" << endl; - - os << endl - << "for t: " << x << "{**.test...}" << endl - << "{" << endl - << " d = $directory($t)" << endl - << " n = $name($t)..." << endl - << endl - << " ./: $d/exe{$n}: $t $d/{" << hs << - "}{+$n} $d/testscript{+$n}" << endl - << " $d/exe{$n}: libue{" << s << "}: bin.whole = false"<< endl - << "}" << endl; - } + switch (l) + { + case lang::c: + { + apih = s + ".h"; + exph = "export.h"; + verh = ver ? "version.h" : string (); - os << endl - << m << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; - os.close (); + // <inc>/<stem>.h + // + open (out_inc / apih); + os << "#pragma once" << '\n' + << '\n' + << "#include <stdio.h>" << '\n' + << '\n' + << "#include <" << ip << exph << ">" << '\n' + << '\n' + << "/* Print a greeting for the specified name into the specified" << '\n' + << " * stream. On success, return the number of characters printed." << '\n' + << " * On failure, set errno and return a negative value." << '\n' + << " */" << '\n' + << mp << "_SYMEXPORT int" << '\n' + << "say_hello (FILE *, const char *name);" << '\n'; + os.close (); - // <base>/.gitignore - // - if (vc == vcs::git) - { - open (sd / ".gitignore"); - os << s << endl; - if (utest) - os << "*.test" << endl; - if (itest || utest) - os << endl - << "# Testscript output directory (can be symlink)." << endl - << "#" << endl; - if (itest) - os << "test-" << s << endl; - if (utest) - os << "test-*.test" << endl; + // <src>/<stem>.c + // + open (out_src / s + ".c"); + os << "#include <" << ip << apih << ">" << '\n' + << '\n' + << "#include <errno.h>" << '\n' + << '\n' + << "int say_hello (FILE *f, const char* n)" << '\n' + << "{" << '\n' + << " if (f == NULL || n == NULL || *n == '\\0')" << '\n' + << " {" << '\n' + << " errno = EINVAL;" << '\n' + << " return -1;" << '\n' + << " }" << '\n' + << '\n' + << " return fprintf (f, \"Hello, %s!\\n\", n);" << '\n' + << "}" << '\n'; os.close (); - } - // <base>/testscript - // - if (itest) + break; + } + case lang::cxx: + if (t.lib_opt.binless ()) { - open (sd / "testscript"); - os << ": basics" << endl - << ":" << endl - << "$* 'World' >'Hello, World!'" << endl - << endl - << ": missing-name" << endl - << ":" << endl - << "$* 2>>EOE != 0" << endl - << "error: missing name" << endl - << "EOE" << endl; + apih = s + he; + verh = ver ? "version" + he : string (); + + // <inc>/<stem>[.<hxx-ext>] + // + open (out_inc / apih); + os << "#pragma once" << '\n' + << '\n' + << "#include <string>" << '\n' + << "#include <ostream>" << '\n' + << "#include <stdexcept>" << '\n' + << '\n' + << "namespace " << id << '\n' + << "{" << '\n' + << " // Print a greeting for the specified name into the specified" << '\n' + << " // stream. Throw std::invalid_argument if the name is empty." << '\n' + << " //" << '\n' + << " inline void" << '\n' + << " say_hello (std::ostream& o, const std::string& name)" << '\n' + << " {" << '\n' + << " using namespace std;" << '\n' + << '\n' + << " if (name.empty ())" << '\n' + << " throw invalid_argument (\"empty name\");" << '\n' + << '\n' + << " o << \"Hello, \" << name << '!' << endl;" << '\n' + << " }" << '\n' + << "}" << '\n'; os.close (); - } - // <base>/<stem>.test.* - // - if (utest) + break; + } + else { - switch (l) - { - case lang::c: - { - // <base>/<stem>.test.c - // - open (sd / s + ".test.c"); - os << "#include <stdio.h>" << endl - << "#include <assert.h>" << endl - << endl - << "int main ()" << endl - << "{" << endl - << " return 0;" << endl - << "}" << endl; - os.close (); + apih = s + he; + exph = "export" + he; + verh = ver ? "version" + he : string (); - break; - } - case lang::cxx: - { - // <base>/<stem>.test.<cxx-ext> - // - open (sd / s + ".test" + xe); - os << "#include <cassert>" << endl - << "#include <iostream>" << endl - << endl - << "int main ()" << endl - << "{" << endl - << endl - << "}" << endl; - os.close (); + // <inc>/<stem>[.<hxx-ext>] + // + open (out_inc / apih); + os << "#pragma once" << '\n' + << '\n' + << "#include <iosfwd>" << '\n' + << "#include <string>" << '\n' + << '\n' + << "#include <" << ip << exph << ">" << '\n' + << '\n' + << "namespace " << id << '\n' + << "{" << '\n' + << " // Print a greeting for the specified name into the specified" << '\n' + << " // stream. Throw std::invalid_argument if the name is empty." << '\n' + << " //" << '\n' + << " " << mp << "_SYMEXPORT void" << '\n' + << " say_hello (std::ostream&, const std::string& name);" << '\n' + << "}" << '\n'; + os.close (); - break; - } - } + // <src>/<stem>.<cxx-ext> + // + open (out_src / s + xe); + os << "#include <" << ip << apih << ">" << '\n' + << '\n' + << "#include <ostream>" << '\n' + << "#include <stdexcept>" << '\n' + << '\n' + << "using namespace std;" << '\n' + << '\n' + << "namespace " << id << '\n' + << "{" << '\n' + << " void say_hello (ostream& o, const string& n)" << '\n' + << " {" << '\n' + << " if (n.empty ())" << '\n' + << " throw invalid_argument (\"empty name\");" << '\n' + << '\n' + << " o << \"Hello, \" << n << '!' << endl;" << '\n' + << " }" << '\n' + << "}" << '\n'; + os.close (); + + break; } + } - break; + // <inc>/export.h[??] + // + if (!exph.empty ()) + { + open (out_inc / exph); + os << "#pragma once" << '\n' + << '\n'; + if (l == lang::cxx) + { + os << "// Normally we don't export class templates (but do complete specializations)," << '\n' + << "// inline functions, and classes with only inline member functions. Exporting" << '\n' + << "// classes that inherit from non-exported/imported bases (e.g., std::string)" << '\n' + << "// will end up badly. The only known workarounds are to not inherit or to not" << '\n' + << "// export. Also, MinGW GCC doesn't like seeing non-exported functions being" << '\n' + << "// used before their inline definition. The workaround is to reorder code. In" << '\n' + << "// the end it's all trial and error." << '\n' + << '\n'; + } + os << "#if defined(" << mp << "_STATIC) // Using static." << '\n' + << "# define " << mp << "_SYMEXPORT" << '\n' + << "#elif defined(" << mp << "_STATIC_BUILD) // Building static." << '\n' + << "# define " << mp << "_SYMEXPORT" << '\n' + << "#elif defined(" << mp << "_SHARED) // Using shared." << '\n' + << "# ifdef _WIN32" << '\n' + << "# define " << mp << "_SYMEXPORT __declspec(dllimport)" << '\n' + << "# else" << '\n' + << "# define " << mp << "_SYMEXPORT" << '\n' + << "# endif" << '\n' + << "#elif defined(" << mp << "_SHARED_BUILD) // Building shared." << '\n' + << "# ifdef _WIN32" << '\n' + << "# define " << mp << "_SYMEXPORT __declspec(dllexport)" << '\n' + << "# else" << '\n' + << "# define " << mp << "_SYMEXPORT" << '\n' + << "# endif" << '\n' + << "#else" << '\n' + << "// If none of the above macros are defined, then we assume we are being used" << '\n' + << "// by some third-party build system that cannot/doesn't signal the library" << '\n' + << "// type. Note that this fallback works for both static and shared libraries" << '\n' + << "// provided the library only exports functions (in other words, no global" << '\n' + << "// exported data) and for the shared case the result will be sub-optimal" << '\n' + << "// compared to having dllimport. If, however, your library does export data," << '\n' + << "// then you will probably want to replace the fallback with the (commented" << '\n' + << "// out) error since it won't work for the shared case." << '\n' + << "//" << '\n' + << "# define " << mp << "_SYMEXPORT // Using static or shared." << '\n' + << "//# error define " << mp << "_STATIC or " << mp << "_SHARED preprocessor macro to signal " << n << " library type being linked" << '\n' + << "#endif" << '\n'; + os.close (); } - case type::lib: + + // <inc>/version.h[??].in + // + if (ver) { - string ip (d.posix_representation ()); // Include prefix. + open (out_inc / verh + ".in"); + + os << "#pragma once" << '\n' + << '\n' + << "// The numeric version format is AAAAABBBBBCCCCCDDDE where:" << '\n' + << "//" << '\n' + << "// AAAAA - major version number" << '\n' + << "// BBBBB - minor version number" << '\n' + << "// CCCCC - bugfix version number" << '\n' + << "// DDD - alpha / beta (DDD + 500) version number" << '\n' + << "// E - final (0) / snapshot (1)" << '\n' + << "//" << '\n' + << "// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:" << '\n' + << "//" << '\n' + << "// Version AAAAABBBBBCCCCCDDDE" << '\n' + << "//" << '\n' + << "// 0.1.0 0000000001000000000" << '\n' + << "// 0.1.2 0000000001000020000" << '\n' + << "// 1.2.3 0000100002000030000" << '\n' + << "// 2.2.0-a.1 0000200001999990010" << '\n' + << "// 3.0.0-b.2 0000299999999995020" << '\n' + << "// 2.2.0-a.1.z 0000200001999990011" << '\n' + << "//" << '\n' + << "#define " << mp << "_VERSION $" << v << ".version.project_number$ULL" << '\n' + << "#define " << mp << "_VERSION_STR \"$" << v << ".version.project$\"" << '\n' + << "#define " << mp << "_VERSION_ID \"$" << v << ".version.project_id$\"" << '\n' + << "#define " << mp << "_VERSION_FULL \"$" << v << ".version$\"" << '\n' + << '\n' + << "#define " << mp << "_VERSION_MAJOR $" << v << ".version.major$" << '\n' + << "#define " << mp << "_VERSION_MINOR $" << v << ".version.minor$" << '\n' + << "#define " << mp << "_VERSION_PATCH $" << v << ".version.patch$" << '\n' + << '\n' + << "#define " << mp << "_PRE_RELEASE $" << v << ".version.pre_release$" << '\n' + << '\n' + << "#define " << mp << "_SNAPSHOT_SN $" << v << ".version.snapshot_sn$ULL" << '\n' + << "#define " << mp << "_SNAPSHOT_ID \"$" << v << ".version.snapshot_id$\"" << '\n'; + os.close (); + } - // Macro prefix. - // - string mp ( - sanitize_identifier (ucase (const_cast<const string&> (ip)))); + bool binless (t.lib_opt.binless ()); - // Strip the trailing underscore (produced from slash). + // <inc>/buildfile + // + if (split) + { + // We shouldn't clash with the root buildfile: we should have failed + // earlier if that were the case. // - mp.pop_back (); + assert (out_inc != out); - string apih; // API header name. - string exph; // Export header name (empty if binless). - string verh; // Version header name. + open (out_inc / buildfile_file); - switch (l) + if (binless) { - case lang::c: - { - apih = s + ".h"; - exph = "export.h"; - verh = ver ? "version.h" : string (); - - // <stem>.h - // - open (sd / apih); - os << "#pragma once" << endl - << endl - << "#include <stdio.h>" << endl - << endl - << "#include <" << ip << exph << ">" << endl - << endl - << "/* Print a greeting for the specified name into the specified" << endl - << " * stream. On success, return the number of characters printed." << endl - << " * On failure, set errno and return a negative value." << endl - << " */" << endl - << mp << "_SYMEXPORT int" << endl - << "say_hello (FILE *, const char *name);" << endl; - os.close (); - - // <stem>.c - // - open (sd / s + ".c"); - os << "#include <" << ip << apih << ">" << endl - << endl - << "#include <errno.h>" << endl - << endl - << "int say_hello (FILE *f, const char* n)" << endl - << "{" << endl - << " if (f == NULL || n == NULL || *n == '\\0')" << endl - << " {" << endl - << " errno = EINVAL;" << endl - << " return -1;" << endl - << " }" << endl - << endl - << " return fprintf (f, \"Hello, %s!\\n\", n);" << endl - << "}" << endl; - os.close (); - - break; - } - case lang::cxx: - if (t.lib_opt.binless ()) - { - apih = s + he; - verh = ver ? "version" + he : string (); + os << "intf_libs = # Interface dependencies." << '\n' + << "impl_libs = # Implementation dependencies." << '\n' + << "#import impl_libs += libhello%lib{hello}" << '\n' + << '\n' + << "lib{" << s << "}: {" << hs << "}{**"; + if (ver) + os << " -version} " << h << "{version}"; + else + os << "}"; + os << " $impl_libs $intf_libs" << '\n'; + } + else + { + os << "pub_hdrs = {" << hs << "}{**"; + if (ver) + os << " -version} " << h << "{version}" << '\n'; + else + os << "}" << '\n'; + os << '\n' + << "./: $pub_hdrs" << '\n'; + } - // <stem>[.<hxx-ext>] - // - open (sd / apih); - os << "#pragma once" << endl - << endl - << "#include <string>" << endl - << "#include <ostream>" << endl - << "#include <stdexcept>" << endl - << endl - << "namespace " << id << endl - << "{" << endl - << " // Print a greeting for the specified name into the specified" << endl - << " // stream. Throw std::invalid_argument if the name is empty." << endl - << " //" << endl - << " inline void" << endl - << " say_hello (std::ostream& o, const std::string& name)" << endl - << " {" << endl - << " using namespace std;" << endl - << endl - << " if (name.empty ())" << endl - << " throw invalid_argument (\"empty name\");" << endl - << endl - << " o << \"Hello, \" << name << '!' << endl;" << endl - << " }" << endl - << "}" << endl; - os.close (); + if (ver) + os << '\n' + << "# Include the generated version header into the distribution (so that we don't" << '\n' + << "# pick up an installed one) and don't remove it when cleaning in src (so that" << '\n' + << "# clean results in a state identical to distributed)." << '\n' + << "#" << '\n' + << h << "{version}: in{version} $src_root/manifest" << '\n' + << "{" << '\n' + << " dist = true" << '\n' + << " clean = ($src_root != $out_root)" << '\n' + << "}" << '\n'; + + if (binless) + { + string pi (pfx_inc.posix_representation ()); + + os << '\n' + << "# Export options." << '\n' + << "#" << '\n'; + + string op (!pi.empty () ? "$out_pfx" : "$out_root"); + string sp (!pi.empty () ? "$src_pfx" : "$src_root"); + + if (!pi.empty ()) + os << "out_pfx = [dir_path] $out_root/" << pi << '\n' + << "src_pfx = [dir_path] $src_root/" << pi << '\n'; + + os << '\n' + << "lib{" << s << "}:" << '\n' + << "{" << '\n' + << " " << m << ".export.poptions = \"-I" << op << "\" " + << "\"-I" << sp << '"' << '\n' + << " " << m << ".export.libs = $intf_libs" << '\n' + << "}" << '\n'; + } - break; - } + if (install) + { + if (!ip.empty ()) + os << '\n' + << "# Install into the " << ip << " subdirectory of, say, /usr/include/" << '\n' + << "# recreating subdirectories." << '\n' + << "#" << '\n'; else - { - apih = s + he; - exph = "export" + he; - verh = ver ? "version" + he : string (); + os << '\n' + << "# Install recreating subdirectories." << '\n' + << "#" << '\n'; + + os << "{" << hs << "}{*}:" << '\n' + << "{" << '\n' + << " install = include/" << ip << '\n' + << " install.subdirs = true" << '\n' + << "}" << '\n'; + } - // <stem>[.<hxx-ext>] - // - open (sd / apih); - os << "#pragma once" << endl - << endl - << "#include <iosfwd>" << endl - << "#include <string>" << endl - << endl - << "#include <" << ip << exph << ">" << endl - << endl - << "namespace " << id << endl - << "{" << endl - << " // Print a greeting for the specified name into the specified" << endl - << " // stream. Throw std::invalid_argument if the name is empty." << endl - << " //" << endl - << " " << mp << "_SYMEXPORT void" << endl - << " say_hello (std::ostream&, const std::string& name);" << endl - << "}" << endl; - os.close (); + os.close (); + } - // <stem>.<cxx-ext> - // - open (sd / s + xe); - os << "#include <" << ip << apih << ">" << endl - << endl - << "#include <ostream>" << endl - << "#include <stdexcept>" << endl - << endl - << "using namespace std;" << endl - << endl - << "namespace " << id << endl - << "{" << endl - << " void say_hello (ostream& o, const string& n)" << endl - << " {" << endl - << " if (n.empty ())" << endl - << " throw invalid_argument (\"empty name\");" << endl - << endl - << " o << \"Hello, \" << n << '!' << endl;" << endl - << " }" << endl - << "}" << endl; - os.close (); + // <src>/buildfile + // + // Note that there is no <src>/buildfile for a splitted binless + // library with the unit tests disabled, since there are no files in + // <src>/ in this case. + // + if (!(binless && !utest && split)) + { + open (out_src / buildfile_file); - break; - } - } + if (!(split && binless)) + os << "intf_libs = # Interface dependencies." << '\n' + << "impl_libs = # Implementation dependencies." << '\n' + << "#import impl_libs += libhello%lib{hello}" << '\n' + << '\n'; - // export.h[??] - // - if (!exph.empty ()) + if (split) { - open (sd / exph); - os << "#pragma once" << endl - << endl; - if (l == lang::cxx) - { - os << "// Normally we don't export class templates (but do complete specializations)," << endl - << "// inline functions, and classes with only inline member functions. Exporting" << endl - << "// classes that inherit from non-exported/imported bases (e.g., std::string)" << endl - << "// will end up badly. The only known workarounds are to not inherit or to not" << endl - << "// export. Also, MinGW GCC doesn't like seeing non-exported functions being" << endl - << "// used before their inline definition. The workaround is to reorder code. In" << endl - << "// the end it's all trial and error." << endl - << endl; - } - os << "#if defined(" << mp << "_STATIC) // Using static." << endl - << "# define " << mp << "_SYMEXPORT" << endl - << "#elif defined(" << mp << "_STATIC_BUILD) // Building static." << endl - << "# define " << mp << "_SYMEXPORT" << endl - << "#elif defined(" << mp << "_SHARED) // Using shared." << endl - << "# ifdef _WIN32" << endl - << "# define " << mp << "_SYMEXPORT __declspec(dllimport)" << endl - << "# else" << endl - << "# define " << mp << "_SYMEXPORT" << endl - << "# endif" << endl - << "#elif defined(" << mp << "_SHARED_BUILD) // Building shared." << endl - << "# ifdef _WIN32" << endl - << "# define " << mp << "_SYMEXPORT __declspec(dllexport)" << endl - << "# else" << endl - << "# define " << mp << "_SYMEXPORT" << endl - << "# endif" << endl - << "#else" << endl - << "// If none of the above macros are defined, then we assume we are being used" << endl - << "// by some third-party build system that cannot/doesn't signal the library" << endl - << "// type. Note that this fallback works for both static and shared libraries" << endl - << "// provided the library only exports functions (in other words, no global" << endl - << "// exported data) and for the shared case the result will be sub-optimal" << endl - << "// compared to having dllimport. If, however, your library does export data," << endl - << "// then you will probably want to replace the fallback with the (commented" << endl - << "// out) error since it won't work for the shared case." << endl - << "//" << endl - << "# define " << mp << "_SYMEXPORT // Using static or shared." << endl - << "//# error define " << mp << "_STATIC or " << mp << "_SHARED preprocessor macro to signal " << n << " library type being linked" << endl - << "#endif" << endl; - os.close (); + string rel (out_inc.relative (out_src).posix_representation ()); + + os << "# Public headers." << '\n' + << "#" << '\n' + << "pub = [dir_path] " << rel << '\n' + << '\n' + << "include $pub" << '\n' + << '\n'; + + if (!binless) + os << "pub_hdrs = $($pub/pub_hdrs)" << '\n' + << '\n'; } - // version.h[??].in + // Add the root buildfile content, if required. // - if (ver) + if (out_src == out) { - open (sd / verh + ".in"); - - os << "#pragma once" << endl - << endl - << "// The numeric version format is AAAAABBBBBCCCCCDDDE where:"<< endl - << "//" << endl - << "// AAAAA - major version number" << endl - << "// BBBBB - minor version number" << endl - << "// CCCCC - bugfix version number" << endl - << "// DDD - alpha / beta (DDD + 500) version number" << endl - << "// E - final (0) / snapshot (1)" << endl - << "//" << endl - << "// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:" << endl - << "//" << endl - << "// Version AAAAABBBBBCCCCCDDDE" << endl - << "//" << endl - << "// 0.1.0 0000000001000000000" << endl - << "// 0.1.2 0000000001000020000" << endl - << "// 1.2.3 0000100002000030000" << endl - << "// 2.2.0-a.1 0000200001999990010" << endl - << "// 3.0.0-b.2 0000299999999995020" << endl - << "// 2.2.0-a.1.z 0000200001999990011" << endl - << "//" << endl - << "#define " << mp << "_VERSION $" << v << ".version.project_number$ULL" << endl - << "#define " << mp << "_VERSION_STR \"$" << v << ".version.project$\"" << endl - << "#define " << mp << "_VERSION_ID \"$" << v << ".version.project_id$\"" << endl - << "#define " << mp << "_VERSION_FULL \"$" << v << ".version$\"" << endl - << endl - << "#define " << mp << "_VERSION_MAJOR $" << v << ".version.major$" << endl - << "#define " << mp << "_VERSION_MINOR $" << v << ".version.minor$" << endl - << "#define " << mp << "_VERSION_PATCH $" << v << ".version.patch$" << endl - << endl - << "#define " << mp << "_PRE_RELEASE $" << v << ".version.pre_release$" << endl - << endl - << "#define " << mp << "_SNAPSHOT_SN $" << v << ".version.snapshot_sn$ULL"<< endl - << "#define " << mp << "_SNAPSHOT_ID \"$" << v << ".version.snapshot_id$\"" << endl; - os.close (); + os << "./: lib{" << s << "} "; + write_doc_prerequisites (true /* newline */); + os << '\n'; } - bool binless (t.lib_opt.binless ()); - - // buildfile - // - open (sd / buildfile_file); - os << "int_libs = # Interface dependencies." << endl - << "imp_libs = # Implementation dependencies." << endl - << "#import imp_libs += libhello%lib{hello}" << endl - << endl; - if (!utest) { - os << "lib{" << s << "}: " << - "{" << hs << (binless ? "" : ' ' + x) << "}{**"; - if (ver) + if (split) + { + assert (!binless); // Make sure pub_hdrs is assigned (see above). + + os << "lib{" << s << "}: $pub/{$pub_hdrs}" << '\n' + << '\n' + << "# Private headers and sources as well as dependencies." << '\n' + << "#" << '\n'; + } + + os << "lib{" << s << "}: " + << "{" << hs << (binless ? "" : ' ' + x) << "}{**"; + + if (ver && !split) os << " -version} " << h << "{version}"; else os << "}"; - os << " $imp_libs $int_libs" << endl; + os << " $impl_libs $intf_libs" << '\n'; } else { - if (binless) + if (!binless) { - os << "./: lib{" << s << "}: " << - "{" << hs << "}{** -**.test..."; - if (ver) - os << " -version} " << h << "{version} \\" << endl - << " "; + if (split) + os << "./: lib{" << s << "}: libul{" << s << "}: $pub/{$pub_hdrs}" << '\n' + << '\n' + << "# Private headers and sources as well as dependencies." << '\n' + << "#" << '\n'; + else + os << "./: lib{" << s << "}: "; + + os << "libul{" << s << "}: " + << "{" << hs << ' ' << x << "}{** -**.test..."; + + if (ver && !split) + os << " -version} \\" << '\n' + << " " << h << "{version}"; else os << "}"; - os << " $imp_libs $int_libs" << endl; + os << " $impl_libs $intf_libs" << '\n' + << '\n'; } - else + else if (!split) // Binless. { - os << "./: lib{" << s << "}: libul{" << s << "}: " << - "{" << hs << ' ' << x << "}{** -**.test..."; + os << "./: lib{" << s << "}: " + << "{" << hs << "}{** -**.test..."; if (ver) - os << " -version} \\" << endl - << " " << h << "{version}"; + os << " -version} " << h << "{version} \\" << '\n' + << " "; else os << "}"; - os << " $imp_libs $int_libs" << endl; + os << " $impl_libs $intf_libs" << '\n' + << '\n'; } - os << endl - << "# Unit tests." << endl - << "#" << endl; + os << "# Unit tests." << '\n' + << "#" << '\n'; if (install) - os << "exe{*.test}:" << endl - << "{" << endl - << " test = true" << endl - << " install = false" << endl - << "}" << endl; + os << "exe{*.test}:" << '\n' + << "{" << '\n' + << " test = true" << '\n' + << " install = false" << '\n' + << "}" << '\n'; else - os << "exe{*.test}: test = true" << endl; + os << "exe{*.test}: test = true" << '\n'; - os << endl - << "for t: " << x << "{**.test...}" << endl - << "{" << endl - << " d = $directory($t)" << endl - << " n = $name($t)..." << endl - << endl - << " ./: $d/exe{$n}: $t $d/{" << hs << "}{+$n} $d/testscript{+$n}"; + // Note that for a splitted binless library all headers in <src>/ + // are presumably for unit tests. + // + os << '\n' + << "for t: " << x << "{**.test...}" << '\n' + << "{" << '\n' + << " d = $directory($t)" << '\n' + << " n = $name($t)..." << '\n' + << '\n' + << " ./: $d/exe{$n}: $t $d/{" << hs << "}{" + << (split && binless ? "**" : "+$n") << "} $d/testscript{+$n}"; if (binless) - os << ' ' << "lib{" << s << "}" << endl; + os << (split ? " $pub/" : " ") << "lib{" << s << "}" << '\n'; else os << '\n' - << " $d/exe{$n}: libul{" << s << "}: bin.whole = false" << endl; + << " $d/exe{$n}: libul{" << s << "}: bin.whole = false" << '\n'; - os << "}" << endl; + os << "}" << '\n'; } - if (ver) - os << endl - << "# Include the generated version header into the distribution (so that we don't" << endl - << "# pick up an installed one) and don't remove it when cleaning in src (so that" << endl - << "# clean results in a state identical to distributed)." << endl - << "#" << endl - << h << "{version}: in{version} $src_root/manifest" << endl - << "{" << endl - << " dist = true" << endl - << " clean = ($src_root != $out_root)" << endl - << "}" << endl; + if (ver && !split) + os << '\n' + << "# Include the generated version header into the distribution (so that we don't" << '\n' + << "# pick up an installed one) and don't remove it when cleaning in src (so that" << '\n' + << "# clean results in a state identical to distributed)." << '\n' + << "#" << '\n' + << h << "{version}: in{version} $src_root/manifest" << '\n' + << "{" << '\n' + << " dist = true" << '\n' + << " clean = ($src_root != $out_root)" << '\n' + << "}" << '\n'; // Build. // - os << endl - << "# Build options." << endl - << "#" << endl - << m << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; + string pi (pfx_inc.posix_representation ()); + string ps (pfx_src.posix_representation ()); - if (!binless) - os << endl - << "obja{*}: " << m << ".poptions += -D" << mp << "_STATIC_BUILD" << endl - << "objs{*}: " << m << ".poptions += -D" << mp << "_SHARED_BUILD" << endl; + os << '\n' + << "# Build options." << '\n' + << "#" << '\n'; - // Export. - // - os << endl - << "# Export options." << endl - << "#" << endl - << "lib{" << s << "}:" << endl - << "{" << endl - << " " << m << ".export.poptions = \"-I$out_root\" \"-I$src_root\"" << endl - << " " << m << ".export.libs = $int_libs" << endl - << "}" << endl; + string opi; + string spi; - if (!binless) - os << endl - << "liba{" << s << "}: " << m << ".export.poptions += -D" << mp << "_STATIC" << endl - << "libs{" << s << "}: " << m << ".export.poptions += -D" << mp << "_SHARED" << endl; + if (split && !binless) + { + opi = !pi.empty () ? "$out_pfx_inc" : "$out_root"; + spi = !pi.empty () ? "$src_pfx_inc" : "$src_root"; - // Library versioning. - // - if (!binless) - os << endl - << "# For pre-releases use the complete version to make sure they cannot be used" << endl - << "# in place of another pre-release or the final version. See the version module" << endl - << "# for details on the version.* variable values." << endl - << "#" << endl - << "if $version.pre_release" << endl - << " lib{" << s << "}: bin.lib.version = @\"-$version.project_id\"" << endl - << "else" << endl - << " lib{" << s << "}: bin.lib.version = @\"-$version.major.$version.minor\"" << endl; + if (!pi.empty ()) + os << "out_pfx_inc = [dir_path] $out_root/" << pi << '\n' + << "src_pfx_inc = [dir_path] $src_root/" << pi << '\n'; - // Installation. - // - if (install) - os << endl - << "# Install into the " << ip << " subdirectory of, say, /usr/include/" << endl - << "# recreating subdirectories." << endl - << "#" << endl - << "{" << hs << "}{*}:" << endl - << "{" << endl - << " install = include/" << ip << endl - << " install.subdirs = true" << endl - << "}" << endl; + string ops (!ps.empty () ? "$out_pfx_src" : "$out_root"); + string sps (!ps.empty () ? "$src_pfx_src" : "$src_root"); - os.close (); + if (!ps.empty ()) + os << "out_pfx_src = [dir_path] $out_root/" << ps << '\n' + << "src_pfx_src = [dir_path] $src_root/" << ps << '\n'; - // <base>/.gitignore - // - if (ver || utest) - { - if (vc == vcs::git) - { - open (sd / ".gitignore"); - if (ver) - os << "# Generated version header." << endl - << "#" << endl - << verh << endl; - if (utest) - os << (ver ? "\n" : "") - << "# Unit test executables and Testscript output directories" << endl - << "# (can be symlinks)." << endl - << "#" << endl - << "*.test" << endl - << "test-*.test" << endl; - os.close (); - } - } + if (!pi.empty () || !ps.empty ()) + os << '\n'; - // <base>/<stem>.test.* - // - if (utest) + os << m << ".poptions =+ \"-I" << ops << "\" \"-I" << sps << "\" \\" << '\n' + << string (m.size () + 13, ' ') + << "\"-I" << opi << "\" \"-I" << spi << '"' << '\n'; + } + else { - switch (l) - { - case lang::c: - { - // <base>/<stem>.test.c - // - open (sd / s + ".test.c"); - os << "#include <stdio.h>" << endl - << "#include <assert.h>" << endl - << endl - << "#include <" << ip << apih << ">" << endl - << endl - << "int main ()" << endl - << "{" << endl - << " return 0;" << endl - << "}" << endl; - os.close (); + opi = !pi.empty () ? "$out_pfx" : "$out_root"; + spi = !pi.empty () ? "$src_pfx" : "$src_root"; - break; - } - case lang::cxx: - { - // <base>/<stem>.test.<cxx-ext> - // - open (sd / s + ".test" + xe); - os << "#include <cassert>" << endl - << "#include <iostream>" << endl - << endl - << "#include <" << ip << apih << ">" << endl - << endl - << "int main ()" << endl - << "{" << endl - << endl - << "}" << endl; - os.close (); + if (!pi.empty ()) + os << "out_pfx = [dir_path] $out_root/" << pi << '\n' + << "src_pfx = [dir_path] $src_root/" << pi << '\n' + << '\n'; - break; - } - } + os << m << ".poptions =+ \"-I" << opi << "\" \"-I" << spi << '"' << '\n'; } - // build/export.build + if (!binless) + os << '\n' + << "obja{*}: " << m << ".poptions += -D" << mp << "_STATIC_BUILD" << '\n' + << "objs{*}: " << m << ".poptions += -D" << mp << "_SHARED_BUILD" << '\n'; + + // Export. // - if (!sub) + if (!(split && binless)) { - open (bd / "export." + build_ext); - os << "$out_root/" << endl - << "{" << endl - << " include " << ip << endl - << "}" << endl - << endl - << "export $out_root/" << ip << "$import.target" << endl; - os.close (); + os << '\n' + << "# Export options." << '\n' + << "#" << '\n' + << "lib{" << s << "}:" << '\n' + << "{" << '\n'; + + os << " " << m << ".export.poptions = \"-I" << opi << "\" " + << "\"-I" << spi << "\"" << '\n' + << " " << m << ".export.libs = $intf_libs" << '\n' + << "}" << '\n'; } - // tests/ (tests subproject). - // - if (!itest) - break; - - dir_path td (dir_path (out) /= "tests"); - mk (td); + if (!binless) + os << '\n' + << "liba{" << s << "}: " << m << ".export.poptions += -D" << mp << "_STATIC" << '\n' + << "libs{" << s << "}: " << m << ".export.poptions += -D" << mp << "_SHARED" << '\n'; - // tests/build/ + // Library versioning. // - dir_path tbd (dir_path (td) / build_dir); - mk (tbd); + if (!binless) + os << '\n' + << "# For pre-releases use the complete version to make sure they cannot be used" << '\n' + << "# in place of another pre-release or the final version. See the version module" << '\n' + << "# for details on the version.* variable values." << '\n' + << "#" << '\n' + << "if $version.pre_release" << '\n' + << " lib{" << s << "}: bin.lib.version = @\"-$version.project_id\"" << '\n' + << "else" << '\n' + << " lib{" << s << "}: bin.lib.version = @\"-$version.major.$version.minor\"" << '\n'; - // tests/build/bootstrap.build + // Installation. // - open (tbd / "bootstrap." + build_ext); - os << "project = # Unnamed tests subproject." << endl - << endl - << "using config" << endl - << "using test" << endl - << "using dist" << endl; + if (install) + { + if (!split) + { + if (!ip.empty ()) + os << '\n' + << "# Install into the " << ip << " subdirectory of, say, /usr/include/" << '\n' + << "# recreating subdirectories." << '\n' + << "#" << '\n'; + else + os << '\n' + << "# Install recreating subdirectories." << '\n' + << "#" << '\n'; + + os << "{" << hs << "}{*}:" << '\n' + << "{" << '\n' + << " install = include/" << ip << '\n' + << " install.subdirs = true" << '\n' + << "}" << '\n'; + } + else + os << '\n' + << "# Don't install private headers." << '\n' + << "#" << '\n' + << "{" << hs << "}{*}: install = false" << '\n'; + } + os.close (); + } - // tests/build/root.build - // - open (tbd / "root." + build_ext); - switch (l) + // <src>/.gitignore + // + // Add the root .gitignore file content at the beginning of our + // .gitignore file, if required. + // + bool root (out_src == out); + + if (ver || utest || root) + { + if (vc == vcs::git) { - case lang::c: + // If the source directory is the project/package root, then it is + // combined (see above). Thus, the version header name will go + // into the same .gitignore file, if its generation is enabled. + // + if (root) { - // @@ TODO: 'latest' in c.std. - // - os //<< "c.std = latest" << endl - //<< endl - << "using c" << endl - << endl - << "h{*}: extension = h" << endl - << "c{*}: extension = c" << endl; - break; + assert (!split); + + open (out_src / ".gitignore"); + write_root_gitignore (); + + if (!ver && !utest) + os.close (); } - case lang::cxx: + + // Note: these can go into a single .gitignore file or be split + // into two for the split case. + // + if (ver) { - os << "cxx.std = latest" << endl - << endl - << "using cxx" << endl - << endl; + if (!os.is_open ()) + open (out_inc / ".gitignore"); + else + os << '\n'; - if (me) os << "mxx{*}: extension = " << pure_ext (*me) << endl; - os << "hxx{*}: extension = " << pure_ext (he) << endl; - if (ie) os << "ixx{*}: extension = " << pure_ext (*ie) << endl; - if (te) os << "txx{*}: extension = " << pure_ext (*te) << endl; - os << "cxx{*}: extension = " << pure_ext (xe) << endl; + os << "# Generated version header." << '\n' + << "#" << '\n' + << verh << '\n'; - break; + if (!utest || split) + os.close (); } - } - os << endl - << "# Every exe{} in this subproject is by default a test."<< endl - << "#" << endl - << "exe{*}: test = true" << endl - << endl - << "# The test target for cross-testing (running tests under Wine, etc)." << endl - << "#" << endl - << "test.target = $" << m << ".target" << endl; - os.close (); - // tests/build/.gitignore - // - if (vc == vcs::git) - { - open (tbd / ".gitignore"); - os << "/config." << build_ext << endl - << "/root/" << endl - << "/bootstrap/" << endl - << "build/" << endl; - os.close (); - } + if (utest) + { + if (!os.is_open ()) + open (out_src / ".gitignore"); + else + os << '\n'; - // tests/buildfile - // - open (td / buildfile_file); - os << "./: {*/ -" << build_dir.posix_representation () << "}" << endl; - os.close (); + os << "# Unit test executables and Testscript output directories" << '\n' + << "# (can be symlinks)." << '\n' + << "#" << '\n' + << "*.test" << '\n' + << "test-*.test" << '\n'; - // tests/.gitignore - // - if (vc == vcs::git) - { - open (td / ".gitignore"); - os << "# Test executables." << endl - << "#" << endl - << "driver" << endl - << endl - << "# Testscript output directories (can be symlinks)." << endl - << "#" << endl - << "test" << endl - << "test-*" << endl; - os.close (); + os.close (); + } } + } - // tests/basics/ - // - td /= "basics"; - mk (td); - + // <src>/<stem>.test.* + // + if (utest) + { switch (l) { case lang::c: { - // It would have been nice if we could use something like - // open_memstream() or fmemopen() but these are not portable. - // So we resort to tmpfile(), which, turns out, is broken on - // Windows (it may try to create a file in the root directory of - // a drive and that may require elevated privileges). So we - // provide our own implementation for that. Who thought writing - // portable C could be so hard? - - // tests/basics/driver.c + // <src>/<stem>.test.c // - open (td / "driver.c"); - os << "#include <stdio.h>" << endl - << "#include <errno.h>" << endl - << "#include <string.h>" << endl - << "#include <assert.h>" << endl - << endl; - if (ver) - os << "#include <" << ip << verh << ">" << endl; - os << "#include <" << ip << apih << ">" << endl - << endl - << "#ifdef _WIN32" << endl - << "#define tmpfile mytmpfile" << endl - << "static FILE *mytmpfile ();" << endl - << "#endif" << endl - << endl - << "int main ()" << endl - << "{" << endl - << " char b[256];" << endl - << endl - << " /* Basics." << endl - << " */" << endl - << " {" << endl - << " FILE *o = tmpfile ();" << endl - << " assert (say_hello (o, \"World\") > 0);" << endl - << " rewind (o);" << endl - << " assert (fread (b, 1, sizeof (b), o) == 14 &&" << endl - << " strncmp (b, \"Hello, World!\\n\", 14) == 0);" << endl - << " fclose (o);" << endl - << " }" << endl - << endl - << " /* Empty name." << endl - << " */" << endl - << " {" << endl - << " FILE *o = tmpfile ();" << endl - << " assert (say_hello (o, \"\") < 0 && errno == EINVAL);" << endl - << " fclose (o);" << endl - << " }" << endl - << endl - << " return 0;" << endl - << "}" << endl - << endl - << "#ifdef _WIN32" << endl - << "#include <windows.h>" << endl - << "#include <fcntl.h>" << endl - << "#include <io.h>" << endl - << endl - << "FILE *mytmpfile ()" << endl - << "{" << endl - << " char d[MAX_PATH + 1], p[MAX_PATH + 1];" << endl - << " if (GetTempPathA (sizeof (d), d) == 0 ||" << endl - << " GetTempFileNameA (d, \"tmp\", 0, p) == 0)" << endl - << " return NULL;" << endl - << endl - << " HANDLE h = CreateFileA (p," << endl - << " GENERIC_READ | GENERIC_WRITE," << endl - << " 0," << endl - << " NULL," << endl - << " CREATE_ALWAYS," << endl - << " FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE," << endl - << " NULL);" << endl - << " if (h == INVALID_HANDLE_VALUE)" << endl - << " return NULL;" << endl - << endl - << " int fd = _open_osfhandle ((intptr_t) h, _O_RDWR);" << endl - << " if (fd == -1)" << endl - << " return NULL;" << endl - << endl - << " FILE *f = _fdopen (fd, \"wb+\");" << endl - << " if (f == NULL)" << endl - << " _close (fd);" << endl - << endl - << " return f;" << endl - << "}" << endl - << "#endif" << endl; + open (out_src / s + ".test.c"); + os << "#include <stdio.h>" << '\n' + << "#include <assert.h>" << '\n' + << '\n' + << "#include <" << ip << apih << ">" << '\n' + << '\n' + << "int main ()" << '\n' + << "{" << '\n' + << " return 0;" << '\n' + << "}" << '\n'; os.close (); break; } case lang::cxx: { - // tests/basics/driver.<cxx-ext> + // <src>/<stem>.test.<cxx-ext> // - open (td / "driver" + xe); - os << "#include <cassert>" << endl - << "#include <sstream>" << endl - << "#include <stdexcept>" << endl - << endl; - if (ver) - os << "#include <" << ip << verh << ">" << endl; - os << "#include <" << ip << apih << ">" << endl - << endl - << "int main ()" << endl - << "{" << endl - << " using namespace std;" << endl - << " using namespace " << id << ";" << endl - << endl - << " // Basics." << endl - << " //" << endl - << " {" << endl - << " ostringstream o;" << endl - << " say_hello (o, \"World\");" << endl - << " assert (o.str () == \"Hello, World!\\n\");" << endl - << " }" << endl - << endl - << " // Empty name." << endl - << " //" << endl - << " try" << endl - << " {" << endl - << " ostringstream o;" << endl - << " say_hello (o, \"\");" << endl - << " assert (false);" << endl - << " }" << endl - << " catch (const invalid_argument& e)" << endl - << " {" << endl - << " assert (e.what () == string (\"empty name\"));" << endl - << " }" << endl - << "}" << endl; + open (out_src / s + ".test" + xe); + os << "#include <cassert>" << '\n' + << "#include <iostream>" << '\n' + << '\n' + << "#include <" << ip << apih << ">" << '\n' + << '\n' + << "int main ()" << '\n' + << "{" << '\n' + << '\n' + << "}" << '\n'; os.close (); break; } } + } - // tests/basics/buildfile + // build/export.build + // + if (!src) + { + // Note: for the binless library the library target is defined in + // the header directory buildfile. // - open (td / buildfile_file); - os << "import libs = " << n << "%lib{" << s << "}" << endl - << endl - << "exe{driver}: {" << hs << ' ' << x << - "}{**} $libs testscript{**}" << endl; - // << endl - // << m << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; + string sd ((binless + ? pfx_inc / (sub_inc ? sub : empty_dir_path) + : pfx_src / (sub_src ? sub : empty_dir_path)). + posix_representation ()); + + + open (bd / "export." + build_ext); + os << "$out_root/" << '\n' + << "{" << '\n' + << " include " << (!sd.empty () ? sd : "./") << '\n' + << "}" << '\n' + << '\n' + << "export $out_root/" << sd << "$import.target" << '\n'; os.close (); + } + // tests/ (tests subproject). + // + if (!itest) break; + + dir_path td (dir_path (out) /= "tests"); + + // tests/build/ + // + dir_path tbd (dir_path (td) / build_dir); + + // tests/build/bootstrap.build + // + open (tbd / "bootstrap." + build_ext); + os << "project = # Unnamed tests subproject." << '\n' + << '\n' + << "using config" << '\n' + << "using test" << '\n' + << "using dist" << '\n'; + os.close (); + + // tests/build/root.build + // + open (tbd / "root." + build_ext); + switch (l) + { + case lang::c: + { + // @@ TODO: 'latest' in c.std. + // + os //<< "c.std = latest" << '\n' + //<< '\n' + << "using c" << '\n' + << '\n' + << "h{*}: extension = h" << '\n' + << "c{*}: extension = c" << '\n'; + break; + } + case lang::cxx: + { + os << "cxx.std = latest" << '\n' + << '\n' + << "using cxx" << '\n' + << '\n'; + + if (me) os << "mxx{*}: extension = " << pure_ext (*me) << '\n'; + os << "hxx{*}: extension = " << pure_ext (he) << '\n'; + if (ie) os << "ixx{*}: extension = " << pure_ext (*ie) << '\n'; + if (te) os << "txx{*}: extension = " << pure_ext (*te) << '\n'; + os << "cxx{*}: extension = " << pure_ext (xe) << '\n'; + + break; + } } - case type::bare: - case type::empty: + os << '\n' + << "# Every exe{} in this subproject is by default a test." << '\n' + << "#" << '\n' + << "exe{*}: test = true" << '\n' + << '\n' + << "# The test target for cross-testing (running tests under Wine, etc)." << '\n' + << "#" << '\n' + << "test.target = $" << m << ".target" << '\n'; + os.close (); + + // tests/build/.gitignore + // + if (vc == vcs::git) { - assert (false); + open (tbd / ".gitignore"); + os << "/config." << build_ext << '\n' + << "/root/" << '\n' + << "/bootstrap/" << '\n' + << "build/" << '\n'; + os.close (); } - } - break; // Done. - } - catch (const io_error& e) - { - fail << "unable to write to " << cf << ": " << e; - } + // tests/buildfile + // + open (td / buildfile_file); + os << "./: {*/ -" << build_dir.posix_representation () << "}" << '\n'; + os.close (); - // Cancel auto-removal of the files we have created. - // - for (auto& rm: rms) - rm.cancel (); + // tests/.gitignore + // + if (vc == vcs::git) + { + open (td / ".gitignore"); + os << "# Test executables." << '\n' + << "#" << '\n' + << "driver" << '\n' + << '\n' + << "# Testscript output directories (can be symlinks)." << '\n' + << "#" << '\n' + << "test" << '\n' + << "test-*" << '\n'; + os.close (); + } - // packages.manifest - // - if (pkg) - { - path f (prj / "packages.manifest"); - bool e (exists (f)); - try - { - ofdstream os (f, (fdopen_mode::out | - fdopen_mode::create | - fdopen_mode::append)); - os << (e ? ":" : ": 1") << endl - << "location: " << pkg->posix_representation () << endl; + // tests/basics/ + // + td /= "basics"; + + switch (l) + { + case lang::c: + { + // It would have been nice if we could use something like + // open_memstream() or fmemopen() but these are not portable. So + // we resort to tmpfile(), which, turns out, is broken on Windows + // (it may try to create a file in the root directory of a drive + // and that may require elevated privileges). So we provide our + // own implementation for that. Who thought writing portable C + // could be so hard? + + // tests/basics/driver.c + // + open (td / "driver.c"); + os << "#include <stdio.h>" << '\n' + << "#include <errno.h>" << '\n' + << "#include <string.h>" << '\n' + << "#include <assert.h>" << '\n' + << '\n'; + if (ver) + os << "#include <" << ip << verh << ">" << '\n'; + os << "#include <" << ip << apih << ">" << '\n' + << '\n' + << "#ifdef _WIN32" << '\n' + << "#define tmpfile mytmpfile" << '\n' + << "static FILE *mytmpfile ();" << '\n' + << "#endif" << '\n' + << '\n' + << "int main ()" << '\n' + << "{" << '\n' + << " char b[256];" << '\n' + << '\n' + << " /* Basics." << '\n' + << " */" << '\n' + << " {" << '\n' + << " FILE *o = tmpfile ();" << '\n' + << " assert (say_hello (o, \"World\") > 0);" << '\n' + << " rewind (o);" << '\n' + << " assert (fread (b, 1, sizeof (b), o) == 14 &&" << '\n' + << " strncmp (b, \"Hello, World!\\n\", 14) == 0);" << '\n' + << " fclose (o);" << '\n' + << " }" << '\n' + << '\n' + << " /* Empty name." << '\n' + << " */" << '\n' + << " {" << '\n' + << " FILE *o = tmpfile ();" << '\n' + << " assert (say_hello (o, \"\") < 0 && errno == EINVAL);" << '\n' + << " fclose (o);" << '\n' + << " }" << '\n' + << '\n' + << " return 0;" << '\n' + << "}" << '\n' + << '\n' + << "#ifdef _WIN32" << '\n' + << "#include <windows.h>" << '\n' + << "#include <fcntl.h>" << '\n' + << "#include <io.h>" << '\n' + << '\n' + << "FILE *mytmpfile ()" << '\n' + << "{" << '\n' + << " char d[MAX_PATH + 1], p[MAX_PATH + 1];" << '\n' + << " if (GetTempPathA (sizeof (d), d) == 0 ||" << '\n' + << " GetTempFileNameA (d, \"tmp\", 0, p) == 0)" << '\n' + << " return NULL;" << '\n' + << '\n' + << " HANDLE h = CreateFileA (p," << '\n' + << " GENERIC_READ | GENERIC_WRITE," << '\n' + << " 0," << '\n' + << " NULL," << '\n' + << " CREATE_ALWAYS," << '\n' + << " FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE," << '\n' + << " NULL);" << '\n' + << " if (h == INVALID_HANDLE_VALUE)" << '\n' + << " return NULL;" << '\n' + << '\n' + << " int fd = _open_osfhandle ((intptr_t) h, _O_RDWR);" << '\n' + << " if (fd == -1)" << '\n' + << " return NULL;" << '\n' + << '\n' + << " FILE *f = _fdopen (fd, \"wb+\");" << '\n' + << " if (f == NULL)" << '\n' + << " _close (fd);" << '\n' + << '\n' + << " return f;" << '\n' + << "}" << '\n' + << "#endif" << '\n'; + os.close (); + + break; + } + case lang::cxx: + { + // tests/basics/driver.<cxx-ext> + // + open (td / "driver" + xe); + os << "#include <cassert>" << '\n' + << "#include <sstream>" << '\n' + << "#include <stdexcept>" << '\n' + << '\n'; + if (ver) + os << "#include <" << ip << verh << ">" << '\n'; + os << "#include <" << ip << apih << ">" << '\n' + << '\n' + << "int main ()" << '\n' + << "{" << '\n' + << " using namespace std;" << '\n' + << " using namespace " << id << ";" << '\n' + << '\n' + << " // Basics." << '\n' + << " //" << '\n' + << " {" << '\n' + << " ostringstream o;" << '\n' + << " say_hello (o, \"World\");" << '\n' + << " assert (o.str () == \"Hello, World!\\n\");" << '\n' + << " }" << '\n' + << '\n' + << " // Empty name." << '\n' + << " //" << '\n' + << " try" << '\n' + << " {" << '\n' + << " ostringstream o;" << '\n' + << " say_hello (o, \"\");" << '\n' + << " assert (false);" << '\n' + << " }" << '\n' + << " catch (const invalid_argument& e)" << '\n' + << " {" << '\n' + << " assert (e.what () == string (\"empty name\"));" << '\n' + << " }" << '\n' + << "}" << '\n'; + os.close (); + + break; + } + } + + // tests/basics/buildfile + // + open (td / buildfile_file); + os << "import libs = " << n << "%lib{" << s << "}" << '\n' + << '\n' + << "exe{driver}: {" << hs << ' ' << x << + "}{**} $libs testscript{**}" << '\n'; + // << '\n' + // << m << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << '\n'; os.close (); + + break; } - catch (const io_error& e) + case type::bare: + case type::empty: { - fail << "unable to write to " << f << ": " << e; + assert (false); } } - // Run post hooks. - // - if (o.post_hook_specified ()) - run_hooks (o.post_hook (), "post"); - - if (verb) - text << "created new " << t << ' ' << (sub ? "source subdirectory" : - pkg ? "package" : "project") - << ' ' << n << " in " << out; + break; // Done. + } + catch (const io_error& e) + { + fail << "unable to write to " << cf << ": " << e; + } - // --no-init | --package | --subdirectory - // - if (o.no_init () || pkg || sub) - return 0; + // Cancel auto-removal of the files we have created. + // + for (auto& rm: rms) + rm.cancel (); - // Create .bdep/. - // - mk (prj / bdep_dir); + // packages.manifest + // + if (pkg) + { + path f (prj / "packages.manifest"); + bool e (exists (f)); + try + { + ofdstream os (f, (fdopen_mode::out | + fdopen_mode::create | + fdopen_mode::append)); + os << (e ? ":" : ": 1") << '\n' + << "location: " << pkg->posix_representation () << '\n'; + os.close (); + } + catch (const io_error& e) + { + fail << "unable to write to " << f << ": " << e; + } + } - // Initialize tmp directory. - // - init_tmp (prj); + // Run post hooks. + // + if (o.post_hook_specified ()) + run_hooks (o.post_hook (), "post"); - // Everything else requires a database. - // - database db (open (prj, trace, true /* create */)); + if (verb) + { + diag_record dr (text); + dr << "created new " << t << ' '; - if (ca || cc) + if (src) { - package_locations pkgs; - - if (t != type::empty) // prj == pkg - pkgs.push_back (package_location {move (pkgn), nullopt, dir_path ()}); - - strings cfg_args; - if (cc) - for (; args.more (); cfg_args.push_back (args.next ())) ; - - configurations cfgs { - cmd_init_config ( - o, - o, - prj, - pkgs, - db, - ca ? o.config_add () : o.config_create (), - cfg_args, - ca, - cc)}; - - cmd_init (o, prj, db, cfgs, pkgs, strings () /* pkg_args */); - } + bool pi (exists (out_inc)); + bool ps (split && exists (out_src)); - return 0; - } + dr << "source subdirectory " << n << " in"; - default_options_files - options_files (const char*, const cmd_new_options& o, const strings&) - { - // NOTE: remember to update the documentation if changing anything here. + if (pi && ps) + dr << "\n " << out_inc + << "\n " << out_src; + else + dr << ' ' << (pi ? out_inc : out_src); + } + else + dr << (pkg ? "package " : "project ") << n << " in " << out; + } - // bdep.options - // bdep-{config config-add}.options # -A - // bdep-{config config-add config-create}.options # -C - // bdep-new.options - // bdep-new-{project|package|subdirectory}.options + // --no-init | --package | --source + // + if (o.no_init () || pkg || src) + return 0; - // Use the project directory as a start directory in the - // package/subdirectory modes and the parent directory of the new project - // otherwise. - // - // Note that we will not validate the command arguments and let cmd_new() - // complain later in case of an error. - // - optional<dir_path> start; + // Create .bdep/. + // + mk (prj / bdep_dir); - auto output_parent_dir = [&o] () - { - return normalize (o.output_dir (), "output"); - }; + // Initialize tmp directory. + // + init_tmp (prj); - if (o.package () || o.subdirectory ()) - { - start = - o.output_dir_specified () ? output_parent_dir () : - o.directory_specified () ? normalize (o.directory (), "project") : - current_directory (); + // Everything else requires a database. + // + database db (open (prj, trace, true /* create */)); - // Get the actual project directory. - // - project_package pp (find_project_package (*start, - true /* ignore_not_found */)); + if (ca || cc) + { + package_locations pkgs; + + if (t != type::empty) // prj == pkg + pkgs.push_back (package_location {move (pkgn), nullopt, dir_path ()}); + + strings cfg_args; + if (cc) + for (; args.more (); cfg_args.push_back (args.next ())) ; + + configurations cfgs { + cmd_init_config ( + o, + o, + prj, + pkgs, + db, + ca ? o.config_add () : o.config_create (), + cfg_args, + ca, + cc)}; + + cmd_init (o, prj, db, cfgs, pkgs, strings () /* pkg_args */); + } - if (!pp.project.empty ()) - start = move (pp.project); - else if (!o.no_checks ()) - start = nullopt; // Let cmd_new() fail. - } - else // New project. - { - start = o.output_dir_specified () - ? output_parent_dir () - : current_directory (); - } + return 0; +} - default_options_files r {{path ("bdep.options")}, move (start)}; +bdep::default_options_files bdep:: +options_files (const char*, const cmd_new_options& o, const strings&) +{ + // NOTE: remember to update the documentation if changing anything here. - auto add = [&r] (const string& n) - { - r.files.push_back (path ("bdep-" + n + ".options")); - }; + // bdep.options + // bdep-{config config-add}.options # -A + // bdep-{config config-add config-create}.options # -C + // bdep-new.options + // bdep-new-{project|package|source}.options - if (o.config_add_specified () || o.config_create_specified ()) - { - add ("config"); - add ("config-add"); - } + // Use the project directory as a start directory in the package/source + // modes and the parent directory of the new project otherwise. + // + // Note that we will not validate the command arguments and let cmd_new() + // complain later in case of an error. + // + optional<dir_path> start; - if (o.config_create_specified ()) - add ("config-create"); + auto output_parent_dir = [&o] () + { + return normalize (o.output_dir (), "output"); + }; - add ("new"); + if (o.package () || o.source ()) + { + start = + o.output_dir_specified () ? output_parent_dir () : + o.directory_specified () ? normalize (o.directory (), "project") : + current_directory (); - // Add the mode-specific options file. + // Get the actual project directory. // - add (o.subdirectory () ? "new-subdirectory" : - o.package () ? "new-package" : - "new-project"); + project_package pp (find_project_package (*start, + true /* ignore_not_found */)); - return r; + if (!pp.project.empty ()) + start = move (pp.project); + else if (!o.no_checks ()) + start = nullopt; // Let cmd_new() fail. } + else // New project. + { + start = o.output_dir_specified () + ? output_parent_dir () + : current_directory (); + } + + default_options_files r {{path ("bdep.options")}, move (start)}; - cmd_new_options - merge_options (const default_options<cmd_new_options>& defs, - const cmd_new_options& cmd) + auto add = [&r] (const string& n) { - // NOTE: remember to update the documentation if changing anything here. + r.files.push_back (path ("bdep-" + n + ".options")); + }; - // While validating/merging the default options, check for the "remote" - // hooks presence and prepare the prompt, if that's the case. - // - diag_record dr; + if (o.config_add_specified () || o.config_create_specified ()) + { + add ("config"); + add ("config-add"); + } - auto verify = [&dr] (const default_options_entry<cmd_new_options>& e, - const cmd_new_options&) - { - const cmd_new_options& o (e.options); + if (o.config_create_specified ()) + add ("config-create"); - auto forbid = [&e] (const char* opt, bool specified) - { - if (specified) - fail (e.file) << opt << " in default options file"; - }; + add ("new"); - forbid ("--output-dir|-o", o.output_dir_specified ()); - forbid ("--directory|-d", o.directory_specified ()); - forbid ("--package", o.package ()); - forbid ("--subdirectory", o.subdirectory ()); - forbid ("--no-checks", o.no_checks ()); - forbid ("--config-add|-A", o.config_add_specified ()); - forbid ("--config-create|-C", o.config_create_specified ()); - forbid ("--wipe", o.wipe ()); // Dangerous. + // Add the mode-specific options file. + // + add (o.source () ? "new-source" : + o.package () ? "new-package" : + "new-project"); - if (e.remote && (o.pre_hook_specified () || o.post_hook_specified ())) - { - if (dr.empty ()) - dr << text; - else - dr << '\n'; + return r; +} - dr << "remote hook commands in " << e.file << ':'; +bdep::cmd_new_options bdep:: +merge_options (const default_options<cmd_new_options>& defs, + const cmd_new_options& cmd) +{ + // NOTE: remember to update the documentation if changing anything here. - auto add = [&dr] (const strings& hs, const char* what) - { - for (const string& cmd: hs) - dr << "\n " << what << ' ' << cmd; - }; + // While validating/merging the default options, check for the "remote" + // hooks presence and prepare the prompt, if that's the case. + // + diag_record dr; - if (o.pre_hook_specified ()) - add (o.pre_hook (), "pre: "); + auto verify = [&dr] (const default_options_entry<cmd_new_options>& e, + const cmd_new_options&) + { + const cmd_new_options& o (e.options); - if (o.post_hook_specified ()) - add (o.post_hook (), "post:"); - } + auto forbid = [&e] (const char* opt, bool specified) + { + if (specified) + fail (e.file) << opt << " in default options file"; }; - cmd_new_options r (merge_default_options (defs, cmd, verify)); + forbid ("--output-dir|-o", o.output_dir_specified ()); + forbid ("--directory|-d", o.directory_specified ()); + forbid ("--package", o.package ()); + forbid ("--source", o.source ()); + forbid ("--no-checks", o.no_checks ()); + forbid ("--config-add|-A", o.config_add_specified ()); + forbid ("--config-create|-C", o.config_create_specified ()); + forbid ("--wipe", o.wipe ()); // Dangerous. - if (!dr.empty ()) + if (e.remote && (o.pre_hook_specified () || o.post_hook_specified ())) { - dr.flush (); + if (dr.empty ()) + dr << text; + else + dr << '\n'; + + dr << "remote hook commands in " << e.file << ':'; - if (!yn_prompt ("execute? [y/n]")) - throw failed (); + auto add = [&dr] (const strings& hs, const char* what) + { + for (const string& cmd: hs) + dr << "\n " << what << ' ' << cmd; + }; + + if (o.pre_hook_specified ()) + add (o.pre_hook (), "pre: "); + + if (o.post_hook_specified ()) + add (o.post_hook (), "post:"); } + }; - return r; + cmd_new_options r (merge_default_options (defs, cmd, verify)); + + if (!dr.empty ()) + { + dr.flush (); + + if (!yn_prompt ("execute? [y/n]")) + throw failed (); } + + return r; } diff --git a/tests/new.testscript b/tests/new.testscript index 8d4b588..34b4dca 100644 --- a/tests/new.testscript +++ b/tests/new.testscript @@ -45,6 +45,66 @@ status += -d prj EOE } + : exe-prefix + : + { + $* -t exe,prefix=src -l c++ prj-foo 2>>/"EOE" &prj-foo/***; + created new executable project prj-foo in $~/prj-foo/ + EOE + + $build prj-foo/ $config_cxx 2>>~%EOE% + %(c\+\+|ld) .+%{2} + EOE + } + + : lib-prefix + : + { + $* -t lib,prefix=src -l c++ libprj-foo 2>>/"EOE" &libprj-foo/***; + created new library project libprj-foo in $~/libprj-foo/ + EOE + + $build libprj-foo/ $config_cxx 2>>~%EOE% + %(version\.in|c\+\+|ar|ld) .+%{7} + EOE + } + + : lib-split + : + { + $* -t lib,split -l c++ libprj-foo 2>>/"EOE" &libprj-foo/***; + created new library project libprj-foo in $~/libprj-foo/ + EOE + + $build libprj-foo/ $config_cxx 2>>~%EOE% + %(version\.in|c\+\+|ar|ld) .+%{7} + EOE + } + + : lib-split-binless + : + { + $* -t lib,split,binless -l c++ libprj-foo 2>>/"EOE" &libprj-foo/***; + created new library project libprj-foo in $~/libprj-foo/ + EOE + + $build libprj-foo/ $config_cxx 2>>~%EOE% + %(version\.in|c\+\+|ld) .+%{3} + EOE + } + + : lib-split-binless-unit-tests + : + { + $* -t lib,split,binless,unit-tests -l c++ libprj-foo 2>>/"EOE" &libprj-foo/***; + created new library project libprj-foo in $~/libprj-foo/ + EOE + + $build libprj-foo/ $config_cxx 2>>~%EOE% + %(version\.in|c\+\+|ld) .+%{5} + EOE + } + : exe-unit-tests : { @@ -173,10 +233,10 @@ status += -d prj EOE } - : lib-alt-source + : lib-alt-subdir : { - $* -l c++ -t lib,source=libprj/foo libprj-foo 2>>/"EOE" &libprj-foo/***; + $* -l c++ -t lib,subdir=libprj/foo libprj-foo 2>>/"EOE" &libprj-foo/***; created new library project libprj-foo in $~/libprj-foo/ EOE @@ -199,6 +259,18 @@ status += -d prj EOE } + : exe-c-prefix + : + { + $* -t exe,prefix=src -l c prj-foo 2>>/"EOE" &prj-foo/***; + created new executable project prj-foo in $~/prj-foo/ + EOE + + $build prj-foo/ $config_c 2>>~%EOE% + %(c|ld) .+%{2} + EOE + } + : exe-c-unit-tests : { @@ -223,6 +295,30 @@ status += -d prj EOE } + : lib-c-prefix + : + { + $* -t lib,prefix=src -l c libprj-foo 2>>/"EOE" &libprj-foo/***; + created new library project libprj-foo in $~/libprj-foo/ + EOE + + $build libprj-foo/ $config_c 2>>~%EOE% + %(version\.in|c|ar|ld) .+%{7} + EOE + } + + : lib-c-split + : + { + $* -t lib,split -l c libprj-foo 2>>/"EOE" &libprj-foo/***; + created new library project libprj-foo in $~/libprj-foo/ + EOE + + $build libprj-foo/ $config_c 2>>~%EOE% + %(version\.in|c|ar|ld) .+%{7} + EOE + } + : lib-c-unit-tests : { @@ -292,6 +388,78 @@ status += -d prj EOE } + : add-prefix + : + : As above but also specify the source subdirectory prefix. + : + { + $* -t empty prj 2>>/"EOE" &prj/***; + created new empty project prj in $~/prj/ + EOE + + $* --package -t lib,prefix=libs/src libprj -d prj 2>>/"EOE"; + created new library package libprj in $~/prj/libprj/ + EOE + + $build prj/libprj/ $config_cxx 2>>~%EOE% + %(version\.in|c\+\+|ar|ld) .+%{7} + EOE + } + + : add-split + : + : As the above 'add' test above but perform the split. + : + { + $* -t empty prj 2>>/"EOE" &prj/***; + created new empty project prj in $~/prj/ + EOE + + $* --package -t lib,split libprj -d prj 2>>/"EOE"; + created new library package libprj in $~/prj/libprj/ + EOE + + $build prj/libprj/ $config_cxx 2>>~%EOE% + %(version\.in|c\+\+|ar|ld) .+%{7} + EOE + } + + : add-split-binless + : + : As above but also binless. + : + { + $* -t empty prj 2>>/"EOE" &prj/***; + created new empty project prj in $~/prj/ + EOE + + $* --package -t lib,split,binless libprj -d prj 2>>/"EOE"; + created new library package libprj in $~/prj/libprj/ + EOE + + $build prj/libprj/ $config_cxx 2>>~%EOE% + %(version\.in|c\+\+|ld) .+%{3} + EOE + } + + : add-split-binless-unit-tests + : + : As above but also with unit-tests. + : + { + $* -t empty prj 2>>/"EOE" &prj/***; + created new empty project prj in $~/prj/ + EOE + + $* --package -t lib,split,binless,unit-tests libprj -d prj 2>>/"EOE"; + created new library package libprj in $~/prj/libprj/ + EOE + + $build prj/libprj/ $config_cxx 2>>~%EOE% + %(version\.in|c\+\+|ld) .+%{5} + EOE + } + : name : : Test that the package/project name is validated. @@ -321,10 +489,10 @@ status += -d prj } } - : sub + : source : { - : exe + : lib : : Test adding a library source subdirectory to an executable project. : @@ -333,7 +501,7 @@ status += -d prj created new executable project prj in $~/prj/ EOE - $* --subdirectory -t lib libprj -d prj 2>>/"EOE"; + $* --source -t lib libprj -d prj 2>>/"EOE"; created new library source subdirectory libprj in $~/prj/libprj/ EOE @@ -346,6 +514,82 @@ status += -d prj EOE } + : lib-prefix + : + : As above but also specify the source subdirectory prefix. + : + { + $* -t exe prj 2>>/"EOE" &prj/***; + created new executable project prj in $~/prj/ + EOE + + $* --source -t lib,prefix=libs/src libprj -d prj 2>>/"EOE"; + created new library source subdirectory libprj in $~/prj/libs/src/libprj/ + EOE + + $build prj/ $config_cxx 2>>~%EOE% + %(c\+\+|ar|ld) .+%{6} + EOE + } + + : lib-split + : + : As the above 'lib' test above but perform the split. + : + { + $* -t exe prj 2>>/"EOE" &prj/***; + created new executable project prj in $~/prj/ + EOE + + $* --source -t lib,split libprj -d prj 2>>/"EOE"; + created new library source subdirectory libprj in + $~/prj/include/libprj/ + $~/prj/src/libprj/ + EOE + + $build prj/ $config_cxx 2>>~%EOE% + %(c\+\+|ar|ld) .+%{6} + EOE + } + + : lib-split-binless + : + : As above but also binless. + : + { + $* -t exe prj 2>>/"EOE" &prj/***; + created new executable project prj in $~/prj/ + EOE + + $* --source -t lib,split,binless libprj -d prj 2>>/"EOE"; + created new library source subdirectory libprj in $~/prj/include/libprj/ + EOE + + $build prj/ $config_cxx 2>>~%EOE% + %(c\+\+|ld) .+%{2} + EOE + } + + : lib-split-binless-unit-tests + : + : As above but also with unit-tests. + : + { + $* -t exe prj 2>>/"EOE" &prj/***; + created new executable project prj in $~/prj/ + EOE + + $* --source -t lib,split,binless,unit-tests libprj -d prj 2>>/"EOE"; + created new library source subdirectory libprj in + $~/prj/include/libprj/ + $~/prj/src/libprj/ + EOE + + $build prj/ $config_cxx 2>>~%EOE% + %(c\+\+|ld) .+%{4} + EOE + } + : bare : : Test filling a bare project with source subdirectories. @@ -355,11 +599,11 @@ status += -d prj created new bare project prj in $~/prj/ EOE - $* --subdirectory -t lib libprj -d prj 2>>/"EOE"; + $* --source -t lib libprj -d prj 2>>/"EOE"; created new library source subdirectory libprj in $~/prj/libprj/ EOE - $* --subdirectory -t exe prj -d prj 2>>/"EOE"; + $* --source -t exe prj -d prj 2>>/"EOE"; created new executable source subdirectory prj in $~/prj/prj/ EOE @@ -377,7 +621,7 @@ status += -d prj created new bare project prj in $~/prj/ EOE - $* --subdirectory -t lib,unit-tests prj -d prj -o prj/core/prj 2>>/"EOE"; + $* --source -t lib,unit-tests prj -d prj -o prj/core/prj 2>>/"EOE"; created new library source subdirectory prj in $~/prj/core/prj/ EOE @@ -800,11 +1044,11 @@ status += -d prj 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@" \ + $* --package -t lib libprj.bash -d prj \ + --post-hook "echo @@@@TODO >>NEWS" \ + --post-hook "echo @mode@ @name@ @base@ @stem@ @root@ @pfx@ @sub@" \ >>/"EOO" 2>>/"EOE" &prj/prj/***; - package libprj.bash libprj prj $~/prj + package libprj.bash libprj prj $~/prj libprj EOO created new library package libprj.bash in $~/prj/libprj.bash/ EOE @@ -815,17 +1059,17 @@ status += -d prj cat <<EOI >=hook #!/bin/sh echo "$(pwd)" - echo "$BDEP_NEW_MODE $BDEP_NEW_ROOT" + echo "$BDEP_NEW_MODE $BDEP_NEW_ROOT $BDEP_NEW_PFX $BDEP_NEW_SUB" 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 + $* -t lib,prefix=src --source libprj -d prj/prj --post-hook "'$~/hook'" \ + >>"EOO" 2>>/"EOE" &prj/prj/src/libprj/*** + $~/prj/prj + source $~/prj/prj src libprj EOO - created new library source subdirectory libprj in $~/prj/prj/libprj/ + created new library source subdirectory libprj in $~/prj/prj/src/libprj/ EOE end } @@ -888,6 +1132,259 @@ status += -d prj EOE } } + + : common-source-layouts + : + : Here we smoke test the common source code layouts listed in bdep-new(1). + : + { + t = $build test: + + : subdir + : + { + $* -l c++ -t lib,subdir=hello libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE%; + %(version\.in|c\+\+|ld|ar) .+%{7} + EOE + + $t libhello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : combined-prefix + : + { + $* -l c++ -t exe,prefix=src hello 2>>/"EOE" &hello/***; + created new executable project hello in $~/hello/ + EOE + + $build hello/ $config_cxx 2>>~%EOE%; + %(c\+\+|ld) .+%{2} + EOE + + $t hello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : include-prefix + : + { + $* -l c++ -t lib,prefix-include=include libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE%; + %(version\.in|c\+\+|ld|ar) .+%{7} + EOE + + $t libhello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : split + : + { + $* -l c++ -t lib,split libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE%; + %(version\.in|c\+\+|ld|ar) .+%{7} + EOE + + $t libhello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : prefix-no-subdir + : + { + $* -l c++ -t exe,prefix=src,no-subdir hello 2>>/"EOE" &hello/***; + created new executable project hello in $~/hello/ + EOE + + $build hello/ $config_cxx 2>>~%EOE%; + %(c\+\+|ld) .+%{2} + EOE + + $t hello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : split-no-subdir + : + { + $* -l c++ -t lib,split,no-subdir,no-version libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE%; + %(c\+\+|ld|ar) .+%{6} + EOE + + $t libhello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : no-subdir + : + { + $* -l c++ -t lib,no-subdir,no-version,no-tests libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE% + %(c\+\+|ld|ar) .+%{4} + EOE + } + + : split-no-subdir-source + : + { + $* -l c++ -t lib,split,subdir=hello,no-subdir-source libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE%; + %(version\.in|c\+\+|ld|ar) .+%{7} + EOE + + $t libhello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : include-in-src + : + { + $* -l c++ \ + -t lib,prefix-include=src/include,prefix-source=src,subdir=hello \ + libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE%; + %(version\.in|c\+\+|ld|ar) .+%{7} + EOE + + $t libhello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : include-in-src-no-subdir-source + : + { + $* -l c++ \ + -t lib,prefix-include=src/include,prefix-source=src,\ +subdir=hello,no-subdir-source \ + libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE%; + %(version\.in|c\+\+|ld|ar) .+%{7} + EOE + + $t libhello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : boost + : + { + $* -l c++ \ + -t lib,prefix-include=include,prefix-source=libs/hello/src,\ +subdir=hello,no-subdir-source \ + libhello 2>>/"EOE" &libhello/***; + created new library project libhello in $~/libhello/ + EOE + + $build libhello/ $config_cxx 2>>~%EOE%; + %(version\.in|c\+\+|ld|ar) .+%{7} + EOE + + $t libhello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + + : multiple-components + : + { + $* -l c++ -t bare hello 2>>/"EOE" &hello/***; + created new bare project hello in $~/hello/ + EOE + + $* -d hello --source \ + -l c++ \ + -t lib,\ +prefix-include=libhello1/include,prefix-source=libhello1/src,\ +subdir=hello1,no-subdir-source \ + libhello1 2>>/"EOE"; + created new library source subdirectory libhello1 in + $~/hello/libhello1/include/hello1/ + $~/hello/libhello1/src/ + EOE + + $* -d hello --source \ + -l c++ \ + -t lib,\ +prefix-include=libhello2/include,prefix-source=libhello2/src,\ +subdir=hello2,no-subdir-source \ + libhello2 2>>/"EOE"; + created new library source subdirectory libhello2 in + $~/hello/libhello2/include/hello2/ + $~/hello/libhello2/src/ + EOE + + $build hello/ $config_cxx 2>>~%EOE% + %(c\+\+|ld|ar) .+%{8} + EOE + } + + : multiple-components-diff-prefixes + : + { + $* -l c++ -t bare hello 2>>/"EOE" &hello/***; + created new bare project hello in $~/hello/ + EOE + + $* -d hello --source \ + -l c++ \ + -t lib,\ +prefix-include=libs/libhello/include,prefix-source=libs/libhello/src,\ +subdir=hello,no-subdir-source \ + libhello 2>>/"EOE"; + created new library source subdirectory libhello in + $~/hello/libs/libhello/include/hello/ + $~/hello/libs/libhello/src/ + EOE + + $* -d hello --source -l c++ -t exe,prefix=src hello 2>>/"EOE"; + created new executable source subdirectory hello in $~/hello/src/hello/ + EOE + + $build hello/ $config_cxx 2>>~%EOE%; + %(c\+\+|ld|ar) .+%{6} + EOE + + $t hello/ $config_cxx 2>>~%EOE% + %test .+% + EOE + } + } } : cfg |