aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-04-05 09:41:18 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2019-04-08 12:51:00 +0200
commit01d848149c22a69a62eada5fedc2406c54d95ba8 (patch)
tree66a3b59619f32f7f7244200f810f4d4cc9115ca5
parent3392226a2248b5cd93a899afb986917ce9e7ad74 (diff)
Support for --dry-run|-n mode, perform update part
-rw-r--r--build2/algorithm.cxx7
-rw-r--r--build2/b-options.cxx77
-rw-r--r--build2/b-options.hxx30
-rw-r--r--build2/b-options.ixx42
-rw-r--r--build2/b.cli65
-rw-r--r--build2/cc/compile-rule.cxx155
-rw-r--r--build2/cc/link-rule.cxx278
-rw-r--r--build2/cc/link-rule.hxx2
-rw-r--r--build2/cc/pkgconfig.cxx10
-rw-r--r--build2/cc/windows-manifest.cxx39
-rw-r--r--build2/cc/windows-rpath.cxx67
-rw-r--r--build2/cli/rule.cxx8
-rw-r--r--build2/context.cxx1
-rw-r--r--build2/context.hxx31
-rw-r--r--build2/depdb.hxx4
-rw-r--r--build2/filesystem.cxx29
-rw-r--r--build2/filesystem.hxx47
-rw-r--r--build2/filesystem.txx15
-rw-r--r--build2/in/rule.cxx11
-rw-r--r--build2/in/rule.hxx3
-rw-r--r--build2/operation.cxx8
-rw-r--r--build2/rule.cxx2
-rw-r--r--build2/rule.hxx3
23 files changed, 567 insertions, 367 deletions
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index 0dffb31..06ed922 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -1074,14 +1074,15 @@ namespace build2
{
// Normally will be there.
//
- try_rmbacklink (l, m);
+ if (!dry_run)
+ try_rmbacklink (l, m);
// Skip (ad hoc) targets that don't exist.
//
if (!(d ? dir_exists (p) : file_exists (p)))
return;
- for (;;) // Retry/fallback loop.
+ for (; !dry_run; ) // Retry/fallback loop.
try
{
switch (m)
@@ -1891,6 +1892,8 @@ namespace build2
// below 3. Note the first extra file/directory that actually got removed
// for diagnostics below.
//
+ // Note that dry-run is taken care of by the filesystem functions.
+ //
target_state er (target_state::unchanged);
bool ed (false);
path ep;
diff --git a/build2/b-options.cxx b/build2/b-options.cxx
index 7622682..a7a348f 100644
--- a/build2/b-options.cxx
+++ b/build2/b-options.cxx
@@ -609,14 +609,14 @@ namespace build2
options ()
: v_ (),
V_ (),
- progress_ (),
- no_progress_ (),
quiet_ (),
verbose_ (1),
verbose_specified_ (false),
stat_ (),
dump_ (),
dump_specified_ (false),
+ progress_ (),
+ no_progress_ (),
jobs_ (),
jobs_specified_ (false),
max_jobs_ (),
@@ -626,10 +626,11 @@ namespace build2
max_stack_ (),
max_stack_specified_ (false),
serial_stop_ (),
+ dry_run_ (),
+ match_only_ (),
+ structured_result_ (),
mtime_check_ (),
no_mtime_check_ (),
- structured_result_ (),
- match_only_ (),
no_column_ (),
no_line_ (),
buildfile_ (),
@@ -729,14 +730,6 @@ namespace build2
<< " equivalent to \033[1m--verbose 3\033[0m." << ::std::endl;
os << std::endl
- << "\033[1m--progress\033[0m Display build progress. If printing to a terminal the" << ::std::endl
- << " progress is displayed by default for low verbosity levels." << ::std::endl
- << " Use \033[1m--no-progress\033[0m to suppress." << ::std::endl;
-
- os << std::endl
- << "\033[1m--no-progress\033[0m Don't display build progress." << ::std::endl;
-
- os << std::endl
<< "\033[1m--quiet\033[0m|\033[1m-q\033[0m Run quietly, only printing error messages. This is" << ::std::endl
<< " equivalent to \033[1m--verbose 0\033[0m." << ::std::endl;
@@ -764,6 +757,14 @@ namespace build2
<< " option to dump the state after multiple phases." << ::std::endl;
os << std::endl
+ << "\033[1m--progress\033[0m Display build progress. If printing to a terminal the" << ::std::endl
+ << " progress is displayed by default for low verbosity levels." << ::std::endl
+ << " Use \033[1m--no-progress\033[0m to suppress." << ::std::endl;
+
+ os << std::endl
+ << "\033[1m--no-progress\033[0m Don't display build progress." << ::std::endl;
+
+ os << std::endl
<< "\033[1m--jobs\033[0m|\033[1m-j\033[0m \033[4mnum\033[0m Number of active jobs to perform in parallel. This" << ::std::endl
<< " includes both the number of active threads inside the" << ::std::endl
<< " build system as well as the number of external commands" << ::std::endl
@@ -809,13 +810,18 @@ namespace build2
<< " default concurrency)." << ::std::endl;
os << std::endl
- << "\033[1m--mtime-check\033[0m Perform file modification time sanity checks. These checks" << ::std::endl
- << " can be helpful in diagnosing spurious rebuilds and are" << ::std::endl
- << " enabled by default for the staged version of the build" << ::std::endl
- << " system. Use \033[1m--no-mtime-check\033[0m to disable." << ::std::endl;
+ << "\033[1m--dry-run\033[0m|\033[1m-n\033[0m Print commands without actually executing them. Note that" << ::std::endl
+ << " commands that are required to create an accurate build" << ::std::endl
+ << " state will still be executed and the extracted auxiliary" << ::std::endl
+ << " dependency information saved. In other words, this is not" << ::std::endl
+ << " the \033[4m\"don't touch the filesystem\"\033[0m mode but rather \033[4m\"do" << ::std::endl
+ << " minimum amount of work to show what needs to be done\"\033[0m." << ::std::endl
+ << " Note also that only the \033[1mperform\033[0m meta-operation supports" << ::std::endl
+ << " this mode." << ::std::endl;
os << std::endl
- << "\033[1m--no-mtime-check\033[0m Don't perform file modification time sanity checks." << ::std::endl;
+ << "\033[1m--match-only\033[0m Match the rules but do not execute the operation. This" << ::std::endl
+ << " mode is primarily useful for profiling." << ::std::endl;
os << std::endl
<< "\033[1m--structured-result\033[0m Write the result of execution in a structured form. In" << ::std::endl
@@ -834,12 +840,17 @@ namespace build2
<< " unchanged perform update(test) /tmp/dir{hello/}" << ::std::endl
<< " changed perform test /tmp/dir{hello/}" << ::std::endl
<< ::std::endl
- << " Currently only the \033[1mperform\033[0m meta-operation supports the" << ::std::endl
+ << " Note that only the \033[1mperform\033[0m meta-operation supports the" << ::std::endl
<< " structured result output." << ::std::endl;
os << std::endl
- << "\033[1m--match-only\033[0m Match the rules but do not execute the operation. This" << ::std::endl
- << " mode is primarily useful for profiling." << ::std::endl;
+ << "\033[1m--mtime-check\033[0m Perform file modification time sanity checks. These checks" << ::std::endl
+ << " can be helpful in diagnosing spurious rebuilds and are" << ::std::endl
+ << " enabled by default for the staged version of the build" << ::std::endl
+ << " system. Use \033[1m--no-mtime-check\033[0m to disable." << ::std::endl;
+
+ os << std::endl
+ << "\033[1m--no-mtime-check\033[0m Don't perform file modification time sanity checks." << ::std::endl;
os << std::endl
<< "\033[1m--no-column\033[0m Don't print column numbers in diagnostics." << ::std::endl;
@@ -911,10 +922,6 @@ namespace build2
&::build2::cl::thunk< options, bool, &options::v_ >;
_cli_options_map_["-V"] =
&::build2::cl::thunk< options, bool, &options::V_ >;
- _cli_options_map_["--progress"] =
- &::build2::cl::thunk< options, bool, &options::progress_ >;
- _cli_options_map_["--no-progress"] =
- &::build2::cl::thunk< options, bool, &options::no_progress_ >;
_cli_options_map_["--quiet"] =
&::build2::cl::thunk< options, bool, &options::quiet_ >;
_cli_options_map_["-q"] =
@@ -927,6 +934,10 @@ namespace build2
_cli_options_map_["--dump"] =
&::build2::cl::thunk< options, std::set<string>, &options::dump_,
&options::dump_specified_ >;
+ _cli_options_map_["--progress"] =
+ &::build2::cl::thunk< options, bool, &options::progress_ >;
+ _cli_options_map_["--no-progress"] =
+ &::build2::cl::thunk< options, bool, &options::no_progress_ >;
_cli_options_map_["--jobs"] =
&::build2::cl::thunk< options, size_t, &options::jobs_,
&options::jobs_specified_ >;
@@ -952,14 +963,18 @@ namespace build2
&::build2::cl::thunk< options, bool, &options::serial_stop_ >;
_cli_options_map_["-s"] =
&::build2::cl::thunk< options, bool, &options::serial_stop_ >;
+ _cli_options_map_["--dry-run"] =
+ &::build2::cl::thunk< options, bool, &options::dry_run_ >;
+ _cli_options_map_["-n"] =
+ &::build2::cl::thunk< options, bool, &options::dry_run_ >;
+ _cli_options_map_["--match-only"] =
+ &::build2::cl::thunk< options, bool, &options::match_only_ >;
+ _cli_options_map_["--structured-result"] =
+ &::build2::cl::thunk< options, bool, &options::structured_result_ >;
_cli_options_map_["--mtime-check"] =
&::build2::cl::thunk< options, bool, &options::mtime_check_ >;
_cli_options_map_["--no-mtime-check"] =
&::build2::cl::thunk< options, bool, &options::no_mtime_check_ >;
- _cli_options_map_["--structured-result"] =
- &::build2::cl::thunk< options, bool, &options::structured_result_ >;
- _cli_options_map_["--match-only"] =
- &::build2::cl::thunk< options, bool, &options::match_only_ >;
_cli_options_map_["--no-column"] =
&::build2::cl::thunk< options, bool, &options::no_column_ >;
_cli_options_map_["--no-line"] =
@@ -1180,9 +1195,9 @@ namespace build2
<< ::std::endl
<< "\033[1mb --help\033[0m" << ::std::endl
<< "\033[1mb --version\033[0m" << ::std::endl
- << "\033[1mb\033[0m [\033[4moptions\033[0m] [\033[4mvariables\033[0m] [\033[4mbuild-spec\033[0m]\033[0m" << ::std::endl
+ << "\033[1mb\033[0m [\033[4moptions\033[0m] [\033[4mvariables\033[0m] [\033[4mbuildspec\033[0m]\033[0m" << ::std::endl
<< ::std::endl
- << "\033[4mbuild-spec\033[0m = \033[4mmeta-operation\033[0m\033[1m(\033[0m\033[4moperation\033[0m\033[1m(\033[0m\033[4mtarget\033[0m...[\033[1m,\033[0m\033[4mparameters\033[0m]\033[1m)\033[0m...\033[1m)\033[0m...\033[0m" << ::std::endl
+ << "\033[4mbuildspec\033[0m = \033[4mmeta-operation\033[0m\033[1m(\033[0m\033[4moperation\033[0m\033[1m(\033[0m\033[4mtarget\033[0m...[\033[1m,\033[0m\033[4mparameters\033[0m]\033[1m)\033[0m...\033[1m)\033[0m...\033[0m" << ::std::endl
<< ::std::endl
<< "\033[1mDESCRIPTION\033[0m" << ::std::endl
<< ::std::endl
@@ -1190,7 +1205,7 @@ namespace build2
<< "according to the build specification, or \033[4mbuildspec\033[0m for short. This process can" << ::std::endl
<< "be controlled by specifying driver \033[4moptions\033[0m and build system \033[4mvariables\033[0m." << ::std::endl
<< ::std::endl
- << "Note that \033[4moptions\033[0m, \033[4mvariables\033[0m, and \033[4mbuild-spec\033[0m fragments can be specified in any" << ::std::endl
+ << "Note that \033[4moptions\033[0m, \033[4mvariables\033[0m, and \033[4mbuildspec\033[0m fragments can be specified in any" << ::std::endl
<< "order. To avoid treating an argument that starts with \033[1m'-'\033[0m as an option, add the" << ::std::endl
<< "\033[1m'--'\033[0m separator. To avoid treating an argument that contains \033[1m'='\033[0m as a variable," << ::std::endl
<< "add the second \033[1m'--'\033[0m separator." << ::std::endl;
diff --git a/build2/b-options.hxx b/build2/b-options.hxx
index 94a87ab..45666aa 100644
--- a/build2/b-options.hxx
+++ b/build2/b-options.hxx
@@ -422,12 +422,6 @@ namespace build2
V () const;
const bool&
- progress () const;
-
- const bool&
- no_progress () const;
-
- const bool&
quiet () const;
const uint16_t&
@@ -445,6 +439,12 @@ namespace build2
bool
dump_specified () const;
+ const bool&
+ progress () const;
+
+ const bool&
+ no_progress () const;
+
const size_t&
jobs () const;
@@ -473,16 +473,19 @@ namespace build2
serial_stop () const;
const bool&
- mtime_check () const;
+ dry_run () const;
const bool&
- no_mtime_check () const;
+ match_only () const;
const bool&
structured_result () const;
const bool&
- match_only () const;
+ mtime_check () const;
+
+ const bool&
+ no_mtime_check () const;
const bool&
no_column () const;
@@ -547,14 +550,14 @@ namespace build2
public:
bool v_;
bool V_;
- bool progress_;
- bool no_progress_;
bool quiet_;
uint16_t verbose_;
bool verbose_specified_;
bool stat_;
std::set<string> dump_;
bool dump_specified_;
+ bool progress_;
+ bool no_progress_;
size_t jobs_;
bool jobs_specified_;
size_t max_jobs_;
@@ -564,10 +567,11 @@ namespace build2
size_t max_stack_;
bool max_stack_specified_;
bool serial_stop_;
+ bool dry_run_;
+ bool match_only_;
+ bool structured_result_;
bool mtime_check_;
bool no_mtime_check_;
- bool structured_result_;
- bool match_only_;
bool no_column_;
bool no_line_;
path buildfile_;
diff --git a/build2/b-options.ixx b/build2/b-options.ixx
index b05ed66..9b7a8ad 100644
--- a/build2/b-options.ixx
+++ b/build2/b-options.ixx
@@ -244,18 +244,6 @@ namespace build2
}
inline const bool& options::
- progress () const
- {
- return this->progress_;
- }
-
- inline const bool& options::
- no_progress () const
- {
- return this->no_progress_;
- }
-
- inline const bool& options::
quiet () const
{
return this->quiet_;
@@ -291,6 +279,18 @@ namespace build2
return this->dump_specified_;
}
+ inline const bool& options::
+ progress () const
+ {
+ return this->progress_;
+ }
+
+ inline const bool& options::
+ no_progress () const
+ {
+ return this->no_progress_;
+ }
+
inline const size_t& options::
jobs () const
{
@@ -346,15 +346,15 @@ namespace build2
}
inline const bool& options::
- mtime_check () const
+ dry_run () const
{
- return this->mtime_check_;
+ return this->dry_run_;
}
inline const bool& options::
- no_mtime_check () const
+ match_only () const
{
- return this->no_mtime_check_;
+ return this->match_only_;
}
inline const bool& options::
@@ -364,9 +364,15 @@ namespace build2
}
inline const bool& options::
- match_only () const
+ mtime_check () const
{
- return this->match_only_;
+ return this->mtime_check_;
+ }
+
+ inline const bool& options::
+ no_mtime_check () const
+ {
+ return this->no_mtime_check_;
}
inline const bool& options::
diff --git a/build2/b.cli b/build2/b.cli
index 35d5e2e..03de044 100644
--- a/build2/b.cli
+++ b/build2/b.cli
@@ -14,15 +14,15 @@ namespace build2
{
"<options>
<variables>
- <build-spec> <meta-operation> <operation> <target> <parameters>",
+ <buildspec> <meta-operation> <operation> <target> <parameters>",
"\h|SYNOPSIS|
\c{\b{b --help}\n
\b{b --version}\n
- \b{b} [<options>] [<variables>] [<build-spec>]}
+ \b{b} [<options>] [<variables>] [<buildspec>]}
- \c{<build-spec> = <meta-operation>\b{(}<operation>\b{(}<target>...[\b{,}<parameters>]\b{)}...\b{)}...}
+ \c{<buildspec> = <meta-operation>\b{(}<operation>\b{(}<target>...[\b{,}<parameters>]\b{)}...\b{)}...}
\h|DESCRIPTION|
@@ -31,7 +31,7 @@ namespace build2
This process can be controlled by specifying driver <options> and build
system <variables>.
- Note that <options>, <variables>, and <build-spec> fragments can be
+ Note that <options>, <variables>, and <buildspec> fragments can be
specified in any order. To avoid treating an argument that starts with
\cb{'-'} as an option, add the \cb{'--'} separator. To avoid treating an
argument that contains \cb{'='} as a variable, add the second \cb{'--'}
@@ -401,18 +401,6 @@ namespace build2
\cb{--verbose 3}."
}
- bool --progress
- {
- "Display build progress. If printing to a terminal the progress is
- displayed by default for low verbosity levels. Use \cb{--no-progress}
- to suppress."
- }
-
- bool --no-progress
- {
- "Don't display build progress."
- }
-
bool --quiet|-q
{
"Run quietly, only printing error messages. This is equivalent to
@@ -456,6 +444,18 @@ namespace build2
state after multiple phases."
}
+ bool --progress
+ {
+ "Display build progress. If printing to a terminal the progress is
+ displayed by default for low verbosity levels. Use \cb{--no-progress}
+ to suppress."
+ }
+
+ bool --no-progress
+ {
+ "Don't display build progress."
+ }
+
size_t --jobs|-j
{
"<num>",
@@ -508,17 +508,21 @@ namespace build2
example \cb{-j\ 0} for default concurrency)."
}
- bool --mtime-check
+ bool --dry-run|-n
{
- "Perform file modification time sanity checks. These checks can be
- helpful in diagnosing spurious rebuilds and are enabled by default
- for the staged version of the build system. Use \cb{--no-mtime-check}
- to disable."
+ "Print commands without actually executing them. Note that commands
+ that are required to create an accurate build state will still be
+ executed and the extracted auxiliary dependency information saved. In
+ other words, this is not the \i{\"don't touch the filesystem\"} mode
+ but rather \i{\"do minimum amount of work to show what needs to be
+ done\"}. Note also that only the \cb{perform} meta-operation supports
+ this mode."
}
- bool --no-mtime-check
+ bool --match-only
{
- "Don't perform file modification time sanity checks."
+ "Match the rules but do not execute the operation. This mode is primarily
+ useful for profiling."
}
bool --structured-result
@@ -540,15 +544,22 @@ namespace build2
changed perform test /tmp/dir{hello/}
\
- Currently only the \cb{perform} meta-operation supports the structured
+ Note that only the \cb{perform} meta-operation supports the structured
result output.
"
}
- bool --match-only
+ bool --mtime-check
{
- "Match the rules but do not execute the operation. This mode is primarily
- useful for profiling."
+ "Perform file modification time sanity checks. These checks can be
+ helpful in diagnosing spurious rebuilds and are enabled by default
+ for the staged version of the build system. Use \cb{--no-mtime-check}
+ to disable."
+ }
+
+ bool --no-mtime-check
+ {
+ "Don't perform file modification time sanity checks."
}
bool --no-column
diff --git a/build2/cc/compile-rule.cxx b/build2/cc/compile-rule.cxx
index 3769b32..3050a37 100644
--- a/build2/cc/compile-rule.cxx
+++ b/build2/cc/compile-rule.cxx
@@ -995,7 +995,15 @@ namespace build2
// We do need to update the database timestamp, however. Failed that,
// we will keep re-validating the cached data over and over again.
//
- if (u && dd.reading ())
+ // @@ DRYRUN: note that for dry-run we would keep re-touching the
+ // database on every run (because u is true). So for now we suppress
+ // it (the file will be re-validated on the real run anyway). It feels
+ // like support for reusing the (partially) preprocessed output (see
+ // note below) should help solve this properly (i.e., we don't want
+ // to keep re-validating the file on every subsequent dry-run as well
+ // on the real run).
+ //
+ if (u && dd.reading () && !dry_run)
dd.touch = true;
dd.close ();
@@ -4642,58 +4650,73 @@ namespace build2
if (verb >= 3)
print_process (args);
- try
+ // @@ DRYRUN: Currently we discard the (partially) preprocessed file on
+ // dry-run which is a waste. Even if we keep the file around (like we do
+ // for the error case; see above), we currently have no support for
+ // re-using the previously preprocessed output. However, everything
+ // points towards us needing this in the near future since with modules
+ // we may be out of date but not needing to re-preprocess the
+ // translation unit (i.e., one of the imported module's has BMIs
+ // changed).
+ //
+ if (!dry_run)
{
- // VC cl.exe sends diagnostics to stdout. It also prints the file name
- // being compiled as the first line. So for cl.exe we redirect stdout
- // to a pipe, filter that noise out, and send the rest to stderr.
- //
- // For other compilers redirect stdout to stderr, in case any of them
- // tries to pull off something similar. For sane compilers this should
- // be harmless.
- //
- bool filter (ctype == compiler_type::msvc);
+ try
+ {
+ // VC cl.exe sends diagnostics to stdout. It also prints the file
+ // name being compiled as the first line. So for cl.exe we redirect
+ // stdout to a pipe, filter that noise out, and send the rest to
+ // stderr.
+ //
+ // For other compilers redirect stdout to stderr, in case any of
+ // them tries to pull off something similar. For sane compilers this
+ // should be harmless.
+ //
+ bool filter (ctype == compiler_type::msvc);
- process pr (cpath,
- args.data (),
- 0, (filter ? -1 : 2), 2,
- nullptr, // CWD
- env.empty () ? nullptr : env.data ());
+ process pr (cpath,
+ args.data (),
+ 0, (filter ? -1 : 2), 2,
+ nullptr, // CWD
+ env.empty () ? nullptr : env.data ());
- if (filter)
- {
- try
+ if (filter)
{
- ifdstream is (
- move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit);
+ try
+ {
+ ifdstream is (
+ move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit);
- msvc_filter_cl (is, *sp);
+ msvc_filter_cl (is, *sp);
- // If anything remains in the stream, send it all to stderr. Note
- // that the eof check is important: if the stream is at eof, this
- // and all subsequent writes to the diagnostics stream will fail
- // (and you won't see a thing).
- //
- if (is.peek () != ifdstream::traits_type::eof ())
- diag_stream_lock () << is.rdbuf ();
+ // If anything remains in the stream, send it all to stderr.
+ // Note that the eof check is important: if the stream is at
+ // eof, this and all subsequent writes to the diagnostics stream
+ // will fail (and you won't see a thing).
+ //
+ if (is.peek () != ifdstream::traits_type::eof ())
+ diag_stream_lock () << is.rdbuf ();
- is.close ();
+ is.close ();
+ }
+ catch (const io_error&) {} // Assume exits with error.
}
- catch (const io_error&) {} // Assume exits with error.
- }
- run_finish (args, pr);
- }
- catch (const process_error& e)
- {
- error << "unable to execute " << args[0] << ": " << e;
+ run_finish (args, pr);
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e;
- if (e.child)
- exit (1);
+ if (e.child)
+ exit (1);
- throw failed ();
+ throw failed ();
+ }
}
+ // Remove preprocessed file (see above).
+ //
if (pact && verb >= 3)
md.psrc.active = true;
@@ -4702,11 +4725,6 @@ namespace build2
//
if (mod && ctype == compiler_type::clang)
{
- // Remove the target file if this fails. If we don't do that, we will
- // end up with a broken build that is up-to-date.
- //
- auto_rmfile rm (relm);
-
// Adjust the command line. First discard everything after -o then
// build the new "tail".
//
@@ -4720,35 +4738,46 @@ namespace build2
if (verb >= 2)
print_process (args);
- try
+ if (!dry_run)
{
- process pr (cpath,
- args.data (),
- 0, 2, 2,
- nullptr, // CWD
- env.empty () ? nullptr : env.data ());
+ // Remove the target file if this fails. If we don't do that, we
+ // will end up with a broken build that is up-to-date.
+ //
+ auto_rmfile rm (relm);
- run_finish (args, pr);
- }
- catch (const process_error& e)
- {
- error << "unable to execute " << args[0] << ": " << e;
+ try
+ {
+ process pr (cpath,
+ args.data (),
+ 0, 2, 2,
+ nullptr, // CWD
+ env.empty () ? nullptr : env.data ());
- if (e.child)
- exit (1);
+ run_finish (args, pr);
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e;
- throw failed ();
- }
+ if (e.child)
+ exit (1);
+
+ throw failed ();
+ }
- rm.cancel ();
+ rm.cancel ();
+ }
}
timestamp now (system_clock::now ());
- depdb::check_mtime (start, md.dd, tp, now);
+
+ if (!dry_run)
+ depdb::check_mtime (start, md.dd, tp, now);
// Should we go to the filesystem and get the new mtime? We know the
// file has been modified, so instead just use the current clock time.
- // It has the advantage of having the subseconds precision.
+ // It has the advantage of having the subseconds precision. Plus, in
+ // case of dry-run, the file won't be modified.
//
t.mtime (now);
return target_state::changed;
diff --git a/build2/cc/link-rule.cxx b/build2/cc/link-rule.cxx
index 8b4d3ee..7d5eb83 100644
--- a/build2/cc/link-rule.cxx
+++ b/build2/cc/link-rule.cxx
@@ -1687,9 +1687,7 @@ namespace build2
auto p (windows_manifest (t, rpath_timestamp != timestamp_nonexistent));
path& mf (p.first);
- bool mf_cf (p.second); // Changed flag (timestamp resolution).
-
- timestamp mf_mt (mtime (mf));
+ timestamp mf_mt (p.second);
if (tsys == "mingw32")
{
@@ -1699,7 +1697,7 @@ namespace build2
//
manifest = mf + ".o";
- if (mf_mt > mtime (manifest) || mf_cf)
+ if (mf_mt == timestamp_nonexistent || mf_mt > mtime (manifest))
{
path of (relative (manifest));
@@ -1717,53 +1715,59 @@ namespace build2
if (verb >= 3)
print_process (args);
- try
+ if (!dry_run)
{
- process pr (rc, args, -1);
+ auto_rmfile rm (of);
try
{
- ofdstream os (move (pr.out_fd));
+ process pr (rc, args, -1);
- // 1 is resource ID, 24 is RT_MANIFEST. We also need to escape
- // Windows path backslashes.
- //
- os << "1 24 \"";
-
- const string& s (mf.string ());
- for (size_t i (0), j;; i = j + 1)
+ try
{
- j = s.find ('\\', i);
- os.write (s.c_str () + i,
- (j == string::npos ? s.size () : j) - i);
+ ofdstream os (move (pr.out_fd));
+
+ // 1 is resource ID, 24 is RT_MANIFEST. We also need to
+ // escape Windows path backslashes.
+ //
+ os << "1 24 \"";
- if (j == string::npos)
- break;
+ const string& s (mf.string ());
+ for (size_t i (0), j;; i = j + 1)
+ {
+ j = s.find ('\\', i);
+ os.write (s.c_str () + i,
+ (j == string::npos ? s.size () : j) - i);
- os.write ("\\\\", 2);
- }
+ if (j == string::npos)
+ break;
+
+ os.write ("\\\\", 2);
+ }
- os << "\"" << endl;
+ os << "\"" << endl;
- os.close ();
+ os.close ();
+ rm.cancel ();
+ }
+ catch (const io_error& e)
+ {
+ if (pr.wait ()) // Ignore if child failed.
+ fail << "unable to pipe resource file to " << args[0]
+ << ": " << e;
+ }
+
+ run_finish (args, pr);
}
- catch (const io_error& e)
+ catch (const process_error& e)
{
- if (pr.wait ()) // Ignore if child failed.
- fail << "unable to pipe resource file to " << args[0]
- << ": " << e;
- }
+ error << "unable to execute " << args[0] << ": " << e;
- run_finish (args, pr);
- }
- catch (const process_error& e)
- {
- error << "unable to execute " << args[0] << ": " << e;
+ if (e.child)
+ exit (1);
- if (e.child)
- exit (1);
-
- throw failed ();
+ throw failed ();
+ }
}
update = true; // Manifest changed, force update.
@@ -1773,7 +1777,7 @@ namespace build2
{
manifest = move (mf); // Save for link.exe's /MANIFESTINPUT.
- if (mf_mt > mt || mf_cf)
+ if (mf_mt == timestamp_nonexistent || mf_mt > mt)
update = true; // Manifest changed, force update.
}
}
@@ -2382,7 +2386,8 @@ namespace build2
args.push_back (nullptr);
- // Cleanup old (versioned) libraries.
+ // Cleanup old (versioned) libraries. Let's do it even for dry-run to
+ // keep things simple.
//
if (lt.shared_library ())
{
@@ -2437,20 +2442,13 @@ namespace build2
// We use relative paths to the object files which means we may end
// up with different ones depending on CWD and some implementation
// treat them as different archive members. So remote the file to
- // be sure. Note that we ignore errors leaving it to the achiever
+ // be sure. Note that we ignore errors leaving it to the archiever
// to complain.
//
if (mt != timestamp_nonexistent)
try_rmfile (relt, true);
}
- // Remove the target file if any of the subsequent (after the linker)
- // actions fail or if the linker fails but does not clean up its mess
- // (like link.exe). If we don't do that, then we will end up with a
- // broken build that is up-to-date.
- //
- auto_rmfile rm (relt);
-
if (verb == 1)
text << (lt.static_library () ? "ar " : "ld ") << t;
else if (verb == 2)
@@ -2562,84 +2560,96 @@ namespace build2
if (verb > 2)
print_process (args);
- try
- {
- // VC tools (both lib.exe and link.exe) send diagnostics to stdout.
- // Also, link.exe likes to print various gratuitous messages. So for
- // link.exe we redirect stdout to a pipe, filter that noise out, and
- // send the rest to stderr.
- //
- // For lib.exe (and any other insane compiler that may try to pull off
- // something like this) we are going to redirect stdout to stderr. For
- // sane compilers this should be harmless.
- //
- bool filter (tsys == "win32-msvc" && !lt.static_library ());
+ // Remove the target file if any of the subsequent (after the linker)
+ // actions fail or if the linker fails but does not clean up its mess
+ // (like link.exe). If we don't do that, then we will end up with a
+ // broken build that is up-to-date.
+ //
+ auto_rmfile rm;
- process pr (*ld, args.data (), 0, (filter ? -1 : 2));
+ if (!dry_run)
+ {
+ rm = auto_rmfile (relt);
- if (filter)
+ try
{
- try
+ // VC tools (both lib.exe and link.exe) send diagnostics to stdout.
+ // Also, link.exe likes to print various gratuitous messages. So for
+ // link.exe we redirect stdout to a pipe, filter that noise out, and
+ // send the rest to stderr.
+ //
+ // For lib.exe (and any other insane linker that may try to pull off
+ // something like this) we are going to redirect stdout to stderr.
+ // For sane compilers this should be harmless.
+ //
+ bool filter (tsys == "win32-msvc" && !lt.static_library ());
+
+ process pr (*ld, args.data (), 0, (filter ? -1 : 2));
+
+ if (filter)
{
- ifdstream is (
- move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit);
+ try
+ {
+ ifdstream is (
+ move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit);
- msvc_filter_link (is, t, ot);
+ msvc_filter_link (is, t, ot);
- // If anything remains in the stream, send it all to stderr. Note
- // that the eof check is important: if the stream is at eof, this
- // and all subsequent writes to the diagnostics stream will fail
- // (and you won't see a thing).
- //
- if (is.peek () != ifdstream::traits_type::eof ())
- diag_stream_lock () << is.rdbuf ();
+ // If anything remains in the stream, send it all to stderr.
+ // Note that the eof check is important: if the stream is at
+ // eof, this and all subsequent writes to the diagnostics stream
+ // will fail (and you won't see a thing).
+ //
+ if (is.peek () != ifdstream::traits_type::eof ())
+ diag_stream_lock () << is.rdbuf ();
- is.close ();
+ is.close ();
+ }
+ catch (const io_error&) {} // Assume exits with error.
}
- catch (const io_error&) {} // Assume exits with error.
- }
- run_finish (args, pr);
- }
- catch (const process_error& e)
- {
- error << "unable to execute " << args[0] << ": " << e;
-
- // In a multi-threaded program that fork()'ed but did not exec(),
- // it is unwise to try to do any kind of cleanup (like unwinding
- // the stack and running destructors).
- //
- if (e.child)
+ run_finish (args, pr);
+ }
+ catch (const process_error& e)
{
- rm.cancel ();
+ error << "unable to execute " << args[0] << ": " << e;
+
+ // In a multi-threaded program that fork()'ed but did not exec(), it
+ // is unwise to try to do any kind of cleanup (like unwinding the
+ // stack and running destructors).
+ //
+ if (e.child)
+ {
+ rm.cancel ();
#ifdef _WIN32
- trm.cancel ();
+ trm.cancel ();
#endif
- exit (1);
- }
+ exit (1);
+ }
- throw failed ();
- }
+ throw failed ();
+ }
- // VC link.exe creates an import library and .exp file for an executable
- // if any of its object files export any symbols (think a unit test
- // linking libus{}). And, no, there is no way to suppress it. Well,
- // there is a way: create a .def file with an empty EXPORTS section,
- // pass it to lib.exe to create a dummy .exp (and .lib), and then pass
- // this empty .exp to link.exe. Wanna go this way? Didn't think so.
- // Having no way to disable this, the next simplest thing seems to be
- // just cleaning the mess up.
- //
- // Note also that if at some point we decide to support such "shared
- // executables" (-rdynamic, etc), then it will probably have to be a
- // different target type (exes{}?) since it will need a different set
- // of object files (-fPIC so probably objs{}), etc.
- //
- if (lt.executable () && tsys == "win32-msvc")
- {
- path b (relt.base ());
- try_rmfile (b + ".lib", true /* ignore_errors */);
- try_rmfile (b + ".exp", true /* ignore_errors */);
+ // VC link.exe creates an import library and .exp file for an
+ // executable if any of its object files export any symbols (think a
+ // unit test linking libus{}). And, no, there is no way to suppress
+ // it. Well, there is a way: create a .def file with an empty EXPORTS
+ // section, pass it to lib.exe to create a dummy .exp (and .lib), and
+ // then pass this empty .exp to link.exe. Wanna go this way? Didn't
+ // think so. Having no way to disable this, the next simplest thing
+ // seems to be just cleaning the mess up.
+ //
+ // Note also that if at some point we decide to support such "shared
+ // executables" (-rdynamic, etc), then it will probably have to be a
+ // different target type (exes{}?) since it will need a different set
+ // of object files (-fPIC so probably objs{}), etc.
+ //
+ if (lt.executable () && tsys == "win32-msvc")
+ {
+ path b (relt.base ());
+ try_rmfile (b + ".lib", true /* ignore_errors */);
+ try_rmfile (b + ".exp", true /* ignore_errors */);
+ }
}
if (ranlib)
@@ -2654,7 +2664,8 @@ namespace build2
if (verb >= 2)
print_process (args);
- run (rl, args);
+ if (!dry_run)
+ run (rl, args);
}
if (tclass == "windows")
@@ -2676,6 +2687,9 @@ namespace build2
if (verb >= 3)
text << "ln -sf " << f << ' ' << l;
+ if (dry_run)
+ return;
+
try
{
if (file_exists (l, false /* follow_symlinks */)) // The -f part.
@@ -2701,29 +2715,35 @@ namespace build2
if (!so.empty ()) {ln (f->leaf (), so); f = &so;}
if (!lk.empty ()) {ln (f->leaf (), lk);}
}
-
- // Apple ar (from cctools) for some reason truncates fractional seconds
- // when running on APFS (HFS has a second resolution so it's not an
- // issue there). This can lead to object files being newer than the
- // archive, which is naturally bad news. Filed as bug 49604334.
- //
- // Note that this block is not inside #ifdef __APPLE__ because we could
- // be cross-compiling, theoretically. We also make sure we use Apple's
- // ar (which is (un)recognized as 'generic') instead of, say, llvm-ar.
- //
- if (lt.static_library () &&
- tsys == "darwin" &&
- cast<string> (rs["bin.ar.id"]) == "generic")
+ else if (lt.static_library ())
{
- touch (tp, false /* create */, verb_never);
+ // Apple ar (from cctools) for some reason truncates fractional
+ // seconds when running on APFS (HFS has a second resolution so it's
+ // not an issue there). This can lead to object files being newer than
+ // the archive, which is naturally bad news. Filed as bug 49604334.
+ //
+ // Note that this block is not inside #ifdef __APPLE__ because we
+ // could be cross-compiling, theoretically. We also make sure we use
+ // Apple's ar (which is (un)recognized as 'generic') instead of, say,
+ // llvm-ar.
+ //
+ if (tsys == "darwin" && cast<string> (rs["bin.ar.id"]) == "generic")
+ {
+ if (!dry_run)
+ touch (tp, false /* create */, verb_never);
+ }
}
- rm.cancel ();
- dd.check_mtime (tp);
+ if (!dry_run)
+ {
+ rm.cancel ();
+ dd.check_mtime (tp);
+ }
// Should we go to the filesystem and get the new mtime? We know the
// file has been modified, so instead just use the current clock time.
- // It has the advantage of having the subseconds precision.
+ // It has the advantage of having the subseconds precision. Plus, in
+ // case of dry-run, the file won't be modified.
//
t.mtime (system_clock::now ());
return target_state::changed;
diff --git a/build2/cc/link-rule.hxx b/build2/cc/link-rule.hxx
index b239dee..1a77aef 100644
--- a/build2/cc/link-rule.hxx
+++ b/build2/cc/link-rule.hxx
@@ -151,7 +151,7 @@ namespace build2
// Windows-specific (windows-manifest.cxx).
//
- pair<path, bool>
+ pair<path, timestamp>
windows_manifest (const file&, bool rpath_assembly) const;
// pkg-config's .pc file generation (pkgconfig.cxx).
diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx
index eef1271..6f30dc9 100644
--- a/build2/cc/pkgconfig.cxx
+++ b/build2/cc/pkgconfig.cxx
@@ -1234,9 +1234,6 @@ namespace build2
auto* t (find_adhoc_member<pc> (l));
assert (t != nullptr);
- const path& p (t->path ());
- auto_rmfile arm (p);
-
// By default we assume things go into install.{include, lib}.
//
using install::resolve_dir;
@@ -1244,9 +1241,16 @@ namespace build2
dir_path idir (resolve_dir (l, cast<dir_path> (l["install.include"])));
dir_path ldir (resolve_dir (l, cast<dir_path> (l["install.lib"])));
+ const path& p (t->path ());
+
if (verb >= 2)
text << "cat >" << p;
+ if (dry_run)
+ return;
+
+ auto_rmfile arm (p);
+
try
{
ofdstream os (p);
diff --git a/build2/cc/windows-manifest.cxx b/build2/cc/windows-manifest.cxx
index 268e8c7..f890ad5 100644
--- a/build2/cc/windows-manifest.cxx
+++ b/build2/cc/windows-manifest.cxx
@@ -37,9 +37,9 @@ namespace build2
// Generate a Windows manifest and if necessary create/update the manifest
// file corresponding to the exe{} target. Return the manifest file path
- // as well as whether it was changed.
+ // and its timestamp if unchanged or timestamp_nonexistent otherwise.
//
- pair<path, bool> link_rule::
+ pair<path, timestamp> link_rule::
windows_manifest (const file& t, bool rpath_assembly) const
{
tracer trace (x, "link_rule::windows_manifest");
@@ -100,13 +100,15 @@ namespace build2
//
path mf (t.path () + ".manifest");
- if (exists (mf))
+ timestamp mt (mtime (mf));
+
+ if (mt != timestamp_nonexistent)
{
try
{
- ifdstream ifs (mf);
- if (ifs.read_text () == m)
- return make_pair (move (mf), false);
+ ifdstream is (mf);
+ if (is.read_text () == m)
+ return make_pair (move (mf), mt);
}
catch (const io_error&)
{
@@ -117,18 +119,25 @@ namespace build2
if (verb >= 3)
text << "cat >" << mf;
- try
- {
- ofdstream ofs (mf);
- ofs << m;
- ofs.close ();
- }
- catch (const io_error& e)
+ if (!dry_run)
{
- fail << "unable to write to " << mf << ": " << e;
+ auto_rmfile rm (mf);
+
+ try
+ {
+ ofdstream os (mf);
+ os << m;
+ os.close ();
+ rm.cancel ();
+
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write to " << mf << ": " << e;
+ }
}
- return make_pair (move (mf), true);
+ return make_pair (move (mf), timestamp_nonexistent);
}
}
}
diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx
index 46fe75b..0a19db2 100644
--- a/build2/cc/windows-rpath.cxx
+++ b/build2/cc/windows-rpath.cxx
@@ -290,23 +290,9 @@ namespace build2
mkdir (ad, 3);
}
- const char* pa (windows_manifest_arch (tcpu));
-
- if (verb >= 3)
- text << "cat >" << am;
-
- try
+ // Symlink or copy the DLLs.
+ //
{
- ofdstream ofs (am);
-
- ofs << "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n"
- << "<assembly xmlns='urn:schemas-microsoft-com:asm.v1'\n"
- << " manifestVersion='1.0'>\n"
- << " <assemblyIdentity name='" << an << "'\n"
- << " type='win32'\n"
- << " processorArchitecture='" << pa << "'\n"
- << " version='0.0.0.0'/>\n";
-
const scope& as (*t.root_scope ().weak_scope ()); // Amalgamation.
auto link = [&as, &ad] (const path& f, const path& l)
@@ -328,15 +314,20 @@ namespace build2
// part of the same amalgamation. This way if the amalgamation is
// moved as a whole, the links will remain valid.
//
- if (f.sub (as.out_path ()))
- mksymlink (f.relative (ad), l);
- else
- mksymlink (f, l);
+ if (!dry_run)
+ {
+ if (f.sub (as.out_path ()))
+ mksymlink (f.relative (ad), l);
+ else
+ mksymlink (f, l);
+ }
print ("ln -s");
}
catch (const system_error& e)
{
+ // Note: can never end up here on dry-run.
+
// Note that we are not guaranteed (here and below) that the
// system_error exception is of the generic category.
//
@@ -378,7 +369,6 @@ namespace build2
}
}
}
-
};
for (const windows_dll& wd: dlls)
@@ -398,13 +388,40 @@ namespace build2
path pp (*wd.pdb);
link (pp, ad / pp.leaf ());
}
-
- ofs << " <file name='" << dn.string () << "'/>\n";
}
+ }
+
+ if (verb >= 3)
+ text << "cat >" << am;
+
+ if (dry_run)
+ return;
+
+ auto_rmfile rm (am);
+
+ try
+ {
+ ofdstream os (am);
+
+ const char* pa (windows_manifest_arch (tcpu));
+
+ os << "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n"
+ << "<assembly xmlns='urn:schemas-microsoft-com:asm.v1'\n"
+ << " manifestVersion='1.0'>\n"
+ << " <assemblyIdentity name='" << an << "'\n"
+ << " type='win32'\n"
+ << " processorArchitecture='" << pa << "'\n"
+ << " version='0.0.0.0'/>\n";
+
+
+
+ for (const windows_dll& wd: dlls)
+ os << " <file name='" << path (wd.dll).leaf () << "'/>\n";
- ofs << "</assembly>\n";
+ os << "</assembly>\n";
- ofs.close ();
+ os.close ();
+ rm.cancel ();
}
catch (const io_error& e)
{
diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx
index cc3e5dd..2da5802 100644
--- a/build2/cli/rule.cxx
+++ b/build2/cli/rule.cxx
@@ -322,9 +322,11 @@ namespace build2
else if (verb)
text << "cli " << s;
- run (cli, args);
-
- dd.check_mtime (tp);
+ if (!dry_run)
+ {
+ run (cli, args);
+ dd.check_mtime (tp);
+ }
t.mtime (system_clock::now ());
return target_state::changed;
diff --git a/build2/context.cxx b/build2/context.cxx
index e1aaeee..aa75b6c 100644
--- a/build2/context.cxx
+++ b/build2/context.cxx
@@ -364,6 +364,7 @@ namespace build2
atomic_count skip_count;
bool keep_going = false;
+ bool dry_run = false;
variable_overrides
reset (const strings& cmd_vars)
diff --git a/build2/context.hxx b/build2/context.hxx
index 74cfa5f..567786d 100644
--- a/build2/context.hxx
+++ b/build2/context.hxx
@@ -402,6 +402,37 @@ namespace build2
//
extern bool keep_going;
+ // Dry run flag (see --dry-run|-n).
+ //
+ // This flag is set only for the final execute phase (as opposed to those
+ // that interrupt match) by the perform meta operation's execute() callback.
+ //
+ // Note that for this mode to function properly we have to use fake mtimes.
+ // Specifically, a rule that pretends to update a target must set its mtime
+ // to system_clock::now() and everyone else must use this cached value. In
+ // other words, there should be no mtime re-query from the filesystem.
+ //
+ // At first, it may seem like we should also "dry-run" changes to depdb. But
+ // that would be both problematic (some rules update it in apply() during
+ // the match phase) and wasteful (why discard information). Also, depdb may
+ // serve as an input to some commands (for example, to provide C++ module
+ // mapping) which means that without updating it the commands we print might
+ // not be runnable (think of the compilation database).
+ //
+ // One thing we need to be careful about if we are updating depdb is to not
+ // render the target up-to-date. But in this case the depdb file will be
+ // older than the target which in our model is treated as an interrupted
+ // update (see depdb for details).
+ //
+ // Note also that sometimes it makes sense to do a bit more than absolutely
+ // necessary or to discard information in order to keep the rule logic sane.
+ // And some rules may choose to ignore this flag altogether. In this case,
+ // however, the rule should be careful not to rely on functions (notably
+ // from filesystem) that respect this flag in order not to end up with a
+ // job half done.
+ //
+ extern bool dry_run;
+
// Reset the build state. In particular, this removes all the targets,
// scopes, and variables.
//
diff --git a/build2/depdb.hxx b/build2/depdb.hxx
index 64ea627..ceb58ac 100644
--- a/build2/depdb.hxx
+++ b/build2/depdb.hxx
@@ -54,7 +54,9 @@ namespace build2
//
// If we assume that an update of the database also means an update of the
// target, then this "interrupted update" situation can be easily detected
- // by comparing the database and target modification timestamps.
+ // by comparing the database and target modification timestamps. This is
+ // also used to handle the dry-run mode where we essentially do the
+ // interruption ourselves.
//
struct depdb_base
{
diff --git a/build2/filesystem.cxx b/build2/filesystem.cxx
index 5e6df5f..7242347 100644
--- a/build2/filesystem.cxx
+++ b/build2/filesystem.cxx
@@ -4,6 +4,7 @@
#include <build2/filesystem.hxx>
+#include <build2/context.hxx>
#include <build2/diagnostics.hxx>
using namespace std;
@@ -11,15 +12,18 @@ using namespace butl;
namespace build2
{
- bool
+ void
touch (const path& p, bool create, uint16_t v)
{
if (verb >= v)
text << "touch " << p;
+ if (dry_run)
+ return;
+
try
{
- return touch_file (p, create);
+ touch_file (p, create);
}
catch (const system_error& e)
{
@@ -112,7 +116,11 @@ namespace build2
try
{
- rs = try_rmsymlink (p, d);
+ rs = dry_run
+ ? (butl::entry_exists (p)
+ ? rmfile_status::success
+ : rmfile_status::not_exist)
+ : try_rmsymlink (p, d);
}
catch (const system_error& e)
{
@@ -140,13 +148,16 @@ namespace build2
if (verb >= v)
text << "rmdir -r " << d;
- try
- {
- butl::rmdir_r (d, dir);
- }
- catch (const system_error& e)
+ if (!dry_run)
{
- fail << "unable to remove directory " << d << ": " << e;
+ try
+ {
+ butl::rmdir_r (d, dir);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to remove directory " << d << ": " << e;
+ }
}
return rmdir_status::success;
diff --git a/build2/filesystem.hxx b/build2/filesystem.hxx
index ed99685..2044141 100644
--- a/build2/filesystem.hxx
+++ b/build2/filesystem.hxx
@@ -12,6 +12,11 @@
// Higher-level filesystem utilities built on top of <libbutl/filesystem.mxx>.
//
+// Compared to the libbutl's versions, these handle errors and issue
+// diagnostics. Some of them also print the corresponding command line
+// equivalent at the specified verbosity level. Note that most of such
+// functions also handle the dry_run flag.
+//
namespace build2
{
using butl::auto_rmfile;
@@ -30,12 +35,12 @@ namespace build2
explicit operator bool () const {return v == T::success;}
};
- // Set the file access and modification times to the current time printing
- // the standard diagnostics starting from the specified verbosity level. If
- // the file does not exist and create is true, create it and fail otherwise.
- // Return true if the file was created and false otherwise.
+ // Set the file access and modification times (unless dry-run) to the
+ // current time printing the standard diagnostics starting from the
+ // specified verbosity level. If the file does not exist and create is true,
+ // create it and fail otherwise.
//
- bool
+ void
touch (const path&, bool create, uint16_t verbosity = 1);
// Return the modification time for an existing regular file and
@@ -51,12 +56,16 @@ namespace build2
return mtime (p.string ().c_str ());
}
- // Create the directory and print the standard diagnostics starting from
- // the specified verbosity level.
+ // Create the directory and print the standard diagnostics starting from the
+ // specified verbosity level.
//
- // Note that this implementation is not suitable if it is expected that the
- // directory will exist in the majority of cases and performance is
- // important. See the fsdir{} rule for details.
+ // Note that these functions ignore the dry_run flag (we might need to save
+ // something in such a directory, such as depdb, ignoring dry_run). Overall,
+ // it feels like we should establish the structure even for dry-run.
+ //
+ // Note that the implementation may not be suitable if the performance is
+ // important and it is expected that the directory will exist in most cases.
+ // See the fsdir{} rule for details.
//
using mkdir_status = butl::mkdir_status;
@@ -66,10 +75,10 @@ namespace build2
fs_status<mkdir_status>
mkdir_p (const dir_path&, uint16_t verbosity = 1);
- // Remove the file and print the standard diagnostics starting from the
- // specified verbosity level. The second argument is only used in
- // diagnostics, to print the target name. Passing the path for target will
- // result in the relative path being printed.
+ // Remove the file (unless dry-run) and print the standard diagnostics
+ // starting from the specified verbosity level. The second argument is only
+ // used in diagnostics, to print the target name. Passing the path for
+ // target will result in the relative path being printed.
//
using rmfile_status = butl::rmfile_status;
@@ -89,6 +98,8 @@ namespace build2
return rmfile (f, f, verbosity);
}
+ // Similar to rmfile() but for symlinks.
+ //
fs_status<rmfile_status>
rmsymlink (const path&, bool dir, uint16_t verbosity);
@@ -112,10 +123,10 @@ namespace build2
return rmdir (d, d, verbosity);
}
- // Remove the directory recursively and print the standard diagnostics
- // starting from the specified verbosity level. Note that this function
- // returns not_empty if we try to remove a working directory. If the dir
- // argument is false, then the directory itself is not removed.
+ // Remove the directory recursively (unless dry-run) and print the standard
+ // diagnostics starting from the specified verbosity level. Note that this
+ // function returns not_empty if we try to remove a working directory. If
+ // the dir argument is false, then the directory itself is not removed.
//
// @@ Collides (via ADL) with butl::rmdir_r(), which sucks.
//
diff --git a/build2/filesystem.txx b/build2/filesystem.txx
index fb224b6..919a26e 100644
--- a/build2/filesystem.txx
+++ b/build2/filesystem.txx
@@ -4,7 +4,7 @@
#include <type_traits> // is_base_of
-#include <build2/context.hxx> // work
+#include <build2/context.hxx>
#include <build2/diagnostics.hxx>
namespace build2
@@ -34,7 +34,9 @@ namespace build2
try
{
- rs = try_rmfile (f);
+ rs = dry_run
+ ? file_exists (f) ? rmfile_status::success : rmfile_status::not_exist
+ : try_rmfile (f);
}
catch (const system_error& e)
{
@@ -54,9 +56,6 @@ namespace build2
{
using namespace butl;
- bool w (work.sub (d)); // Don't try to remove working directory.
- rmdir_status rs;
-
// We don't want to print the command if we couldn't remove the directory
// because it does not exist (just like we don't print mkdir if it already
// exists) or if it is not empty. This makes the below code a bit ugly.
@@ -72,9 +71,13 @@ namespace build2
}
};
+ bool w (false); // Don't try to remove working directory.
+ rmdir_status rs;
try
{
- rs = !w ? try_rmdir (d) : rmdir_status::not_empty;
+ rs = dry_run
+ ? dir_exists (d) ? rmdir_status::success : rmdir_status::not_exist
+ : !(w = work.sub (d)) ? try_rmdir (d) : rmdir_status::not_empty;
}
catch (const system_error& e)
{
diff --git a/build2/in/rule.cxx b/build2/in/rule.cxx
index 2f8094d..a6d4f2c 100644
--- a/build2/in/rule.cxx
+++ b/build2/in/rule.cxx
@@ -179,6 +179,9 @@ namespace build2
// For now we assume this is ok since this is probably not very common
// and it makes the overall logic simpler.
//
+ // Note also that because updating the depdb essentially requires
+ // performing the substitutions, this rule ignored the dry-run mode.
+ //
size_t dd_skip (0); // Number of "good" variable lines.
if (update)
@@ -260,7 +263,7 @@ namespace build2
else if (verb)
text << program_ << ' ' << ip;
- // Read and process the file, one line at a time.
+ // Read and process the file, one line at a time, while updating depdb.
//
const char* what;
const path* whom;
@@ -278,9 +281,11 @@ namespace build2
if (t.is_a<exe> ())
prm |= permissions::xu | permissions::xg | permissions::xo;
- // Remove the existing file to make sure permissions take effect.
+ // Remove the existing file to make sure permissions take effect. If
+ // this fails then presumable writing to it will fail as well and we
+ // will complain there.
//
- rmfile (tp, 3 /* verbosity */);
+ try_rmfile (tp, true /* ignore_error */);
what = "open"; whom = &tp;
ofdstream ofs (fdopen (tp,
diff --git a/build2/in/rule.hxx b/build2/in/rule.hxx
index 001fc66..b3430c5 100644
--- a/build2/in/rule.hxx
+++ b/build2/in/rule.hxx
@@ -19,6 +19,9 @@ namespace build2
// Note that a derived rule can use the target data pad to cache data
// (e.g., in match()) to be used in substitute/lookup() calls.
//
+ // Note also that currently this rule ignores the dry-run mode (see
+ // perform_update() for the rationale).
+ //
class rule: public build2::rule
{
public:
diff --git a/build2/operation.cxx b/build2/operation.cxx
index 74db2f3..9bc8a4e 100644
--- a/build2/operation.cxx
+++ b/build2/operation.cxx
@@ -269,6 +269,10 @@ namespace build2
phase_lock pl (run_phase::execute); // Never switched.
+ // Set the dry-run flag.
+ //
+ dry_run = ops.dry_run ();
+
// Setup progress reporting if requested.
//
string what; // Note: must outlive monitor_guard.
@@ -338,6 +342,10 @@ namespace build2
sched.tune (0); // Restore original scheduler settings.
+ // Clear the dry-run flag.
+ //
+ dry_run = false;
+
// Clear the progress if present.
//
if (mg)
diff --git a/build2/rule.cxx b/build2/rule.cxx
index 2a10a90..79b91b3 100644
--- a/build2/rule.cxx
+++ b/build2/rule.cxx
@@ -196,6 +196,8 @@ namespace build2
text << "mkdir " << t;
};
+ // Note: ignoring the dry_run flag.
+ //
mkdir_status ms;
try
diff --git a/build2/rule.hxx b/build2/rule.hxx
index b6c0154..a9bc178 100644
--- a/build2/rule.hxx
+++ b/build2/rule.hxx
@@ -58,6 +58,9 @@ namespace build2
static const alias_rule instance;
};
+ // Note that this rule ignores the dry_run flag; see mkdir() in filesystem
+ // for the rationale.
+ //
class fsdir_rule: public rule
{
public: