aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2020-07-08 21:48:05 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2020-07-16 15:46:43 +0300
commit3aa122ed0fd598c4854b3d55f775e06a59112151 (patch)
treef776b3c08dd67359b43984e27769bcc082e454bb
parentc004b0f6b8dd0ede917b90060fb0c97a91a239d2 (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.cli527
-rw-r--r--bdep/new.cxx4261
-rw-r--r--tests/new.testscript533
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, &copyright_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