diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2019-04-05 09:41:18 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2019-04-08 12:51:00 +0200 |
commit | 01d848149c22a69a62eada5fedc2406c54d95ba8 (patch) | |
tree | 66a3b59619f32f7f7244200f810f4d4cc9115ca5 | |
parent | 3392226a2248b5cd93a899afb986917ce9e7ad74 (diff) |
Support for --dry-run|-n mode, perform update part
-rw-r--r-- | build2/algorithm.cxx | 7 | ||||
-rw-r--r-- | build2/b-options.cxx | 77 | ||||
-rw-r--r-- | build2/b-options.hxx | 30 | ||||
-rw-r--r-- | build2/b-options.ixx | 42 | ||||
-rw-r--r-- | build2/b.cli | 65 | ||||
-rw-r--r-- | build2/cc/compile-rule.cxx | 155 | ||||
-rw-r--r-- | build2/cc/link-rule.cxx | 278 | ||||
-rw-r--r-- | build2/cc/link-rule.hxx | 2 | ||||
-rw-r--r-- | build2/cc/pkgconfig.cxx | 10 | ||||
-rw-r--r-- | build2/cc/windows-manifest.cxx | 39 | ||||
-rw-r--r-- | build2/cc/windows-rpath.cxx | 67 | ||||
-rw-r--r-- | build2/cli/rule.cxx | 8 | ||||
-rw-r--r-- | build2/context.cxx | 1 | ||||
-rw-r--r-- | build2/context.hxx | 31 | ||||
-rw-r--r-- | build2/depdb.hxx | 4 | ||||
-rw-r--r-- | build2/filesystem.cxx | 29 | ||||
-rw-r--r-- | build2/filesystem.hxx | 47 | ||||
-rw-r--r-- | build2/filesystem.txx | 15 | ||||
-rw-r--r-- | build2/in/rule.cxx | 11 | ||||
-rw-r--r-- | build2/in/rule.hxx | 3 | ||||
-rw-r--r-- | build2/operation.cxx | 8 | ||||
-rw-r--r-- | build2/rule.cxx | 2 | ||||
-rw-r--r-- | build2/rule.hxx | 3 |
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: |