From 9c0dc1f4957420688cf2c1afe79fa2f53f2a6abf Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 5 Sep 2018 00:00:14 +0300 Subject: Create .buildignore file in testscript root working directory --- build2/filesystem.cxx | 59 ++++++++++++++++++++++++++++ build2/filesystem.hxx | 22 +++++++++++ build2/parser.cxx | 3 +- build2/test/rule.cxx | 14 +++++-- build2/test/script/runner.cxx | 26 ++++++++---- tests/name/pattern.testscript | 15 +++++++ tests/test/script/integration/testscript | 8 ++-- tests/test/script/runner/redirect.testscript | 4 ++ 8 files changed, 136 insertions(+), 15 deletions(-) diff --git a/build2/filesystem.cxx b/build2/filesystem.cxx index 47a711f..8b4e3ec 100644 --- a/build2/filesystem.cxx +++ b/build2/filesystem.cxx @@ -189,4 +189,63 @@ namespace build2 fail << "unable to scan directory " << d << ": " << e << endf; } } + + const path buildignore (".buildignore"); + + fs_status + mkdir_buildignore (const dir_path& d, uint16_t verbosity) + { + fs_status r (mkdir (d, verbosity)); + + // Create the .buildignore file if the directory was created (and so is + // empty) or the file doesn't exist. + // + path p (d / buildignore); + if (r || !exists (p)) + touch (p, true /* create */, verbosity); + + return r; + } + + bool + empty_buildignore (const dir_path& d) + { + try + { + for (const dir_entry& de: dir_iterator (d, false /* ignore_dangling */)) + { + // The .buildignore filesystem entry should be of the regular file + // type. + // + if (de.path () != buildignore || de.ltype () != entry_type::regular) + return false; + } + } + catch (const system_error& e) + { + fail << "unable to scan directory " << d << ": " << e; + } + + return true; + } + + fs_status + rmdir_buildignore (const dir_path& d, uint16_t verbosity) + { + // We should remove the .buildignore file only if the subsequent rmdir() + // will succeed. In other words if the directory stays after the function + // call then the .buildignore file must stay also, if present. Thus, we + // first check that the directory is otherwise empty and doesn't contain + // the working directory. + // + path p (d / buildignore); + if (exists (p) && empty_buildignore (d) && !work.sub (d)) + rmfile (p, verbosity); + + // Note that in case of a system error the directory is likely to stay and + // the .buildfile is already removed. Trying to restore it feels like an + // overkill here. + // + return rmdir (d, verbosity); + } } diff --git a/build2/filesystem.hxx b/build2/filesystem.hxx index fe98263..3bcd807 100644 --- a/build2/filesystem.hxx +++ b/build2/filesystem.hxx @@ -128,6 +128,28 @@ namespace build2 // bool empty (const dir_path&); + + // Directories containing .buildignore file are automatically ignored by + // recursive names patterns. For now the file is just a marker and its + // contents don't matter. + // + extern const path buildignore; // .buildignore + + // Create a directory containing an empty .buildignore file. + // + fs_status + mkdir_buildignore (const dir_path&, uint16_t verbosity = 1); + + // Return true if the directory is empty or only contains the .buildignore + // file. Fail if the directory doesn't exist. + // + bool + empty_buildignore (const dir_path&); + + // Remove a directory if it is empty or only contains the .buildignore file. + // + fs_status + rmdir_buildignore (const dir_path&, uint16_t verbosity = 1); } #include diff --git a/build2/parser.cxx b/build2/parser.cxx index c6b6b45..3c8e3ac 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -3028,7 +3029,7 @@ namespace build2 // const string& s (m.string ()); if ((p[0] != '.' && s[path::traits::find_leaf (s)] == '.') || - (m.to_directory () && file_exists (*sp / m / ".buildignore"))) + (m.to_directory () && exists (*sp / m / buildignore))) return !interm; // Note that we have to make copies of the extension since there will diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx index 64cd56c..0b67d68 100644 --- a/build2/test/rule.cxx +++ b/build2/test/rule.cxx @@ -450,6 +450,10 @@ namespace build2 // the root directory is used directly as test's working directory and // it's the runner's responsibility to create and clean it up. // + // Note that we create the root directory containing the .buildignore + // file to make sure that it is ignored by name patterns (see the + // buildignore description for details). + // // What should we do if the directory already exists? We used to fail // which meant the user had to go and clean things up manually every // time a test failed. This turned out to be really annoying. So now we @@ -467,7 +471,9 @@ namespace build2 bool fail (before == output_before::fail); (fail ? error : warn) << "working directory " << wd << " exists " - << (empty (wd) ? "" : "and is not empty ") + << (empty_buildignore (wd) + ? "" + : "and is not empty ") << "at the beginning of the test"; if (fail) throw failed (); @@ -507,7 +513,7 @@ namespace build2 { if (mk) { - mkdir (wd, 2); + mkdir_buildignore (wd, 2); mk = false; } @@ -562,11 +568,11 @@ namespace build2 // if (!bad && !one && !mk && after == output_after::clean) { - if (!empty (wd)) + if (!empty_buildignore (wd)) fail << "working directory " << wd << " is not empty at the " << "end of the test"; - rmdir (wd, 2); + rmdir_buildignore (wd, 2); } // Backlink if the working directory exists. diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx index 0ceed5b..dfe4981 100644 --- a/build2/test/script/runner.cxx +++ b/build2/test/script/runner.cxx @@ -701,11 +701,19 @@ namespace build2 // directory is cleaned up by the test rule prior the script // execution). // + // Create the root working directory containing the .buildignore file + // to make sure that it is ignored by name patterns (see buildignore + // description for details). + // // @@ Shouldn't we add an optional location parameter to mkdir() and // alike utility functions so the failure message can contain // location info? // - if (mkdir (sp.wd_path, 2) == mkdir_status::already_exists) + fs_status r (sp.parent == nullptr + ? mkdir_buildignore (sp.wd_path, 2) + : mkdir (sp.wd_path, 2)); + + if (r == mkdir_status::already_exists) fail << "working directory " << sp.wd_path << " already exists" << info << "are tests stomping on each other's feet?"; @@ -865,27 +873,31 @@ namespace build2 if (p.to_directory ()) { dir_path d (path_cast (p)); + bool wd (d == sp.wd_path); // Trace the scope working directory removal with the verbosity // level 2 (that was used for its creation). For other // directories use level 3 (as for other cleanups). // - int v (d == sp.wd_path ? 2 : 3); + int v (wd ? 2 : 3); // Don't remove the working directory for the recursive cleanup // (it will be removed by the dedicated one). // + // Note that the root working directory contains the + // .buildignore file (see above). + // // @@ If 'd' is a file then will fail with a diagnostics having // no location info. Probably need to add an optional location // parameter to rmdir() function. The same problem exists for // a file cleanup when try to rmfile() directory instead of // file. // - rmdir_status r (!recursive - ? rmdir (d, v) - : rmdir_r (d, - d != sp.wd_path, - static_cast (v))); + rmdir_status r (recursive + ? rmdir_r (d, !wd, static_cast (v)) + : wd && sp.parent == nullptr + ? rmdir_buildignore (d, v) + : rmdir (d, v)); if (r == rmdir_status::success || (r == rmdir_status::not_exist && t == cleanup_type::maybe)) diff --git a/tests/name/pattern.testscript b/tests/name/pattern.testscript index d40e3a4..1c9d6f5 100644 --- a/tests/name/pattern.testscript +++ b/tests/name/pattern.testscript @@ -181,6 +181,21 @@ EOI $* <'print .*/*.txt' >/'.dir/foo.txt' : dir-interm-incl } +: buildignore +: +: Test filtering of a directory and its sub-entries if it contains the +: .buildignore file. +: +{ + mkdir dir1 dir2; + touch dir2/.buildignore; + $* <'print */' >/'dir1/' : self-excl + + mkdir dir1 dir2; + touch dir1/foo dir2/foo dir2/.buildignore; + $* <'print f**' >/'dir1/foo' : sub-entry-excl +} + : expansion : : Test interaction with expansion/concatenation/re-parse. diff --git a/tests/test/script/integration/testscript b/tests/test/script/integration/testscript index 7bdad54..b399197 100644 --- a/tests/test/script/integration/testscript +++ b/tests/test/script/integration/testscript @@ -50,7 +50,8 @@ EOE : wd-exists-before : touch foo.testscript; -mkdir test &!test/; +mkdir --no-cleanup test; +touch --no-cleanup test/.buildignore; $* <>/EOE ./: testscript{foo} EOI @@ -60,8 +61,9 @@ EOE : wd-not-empty-before : touch foo.testscript; -mkdir test &!test/; -touch test/dummy &!test/dummy; +mkdir --no-cleanup test; +touch --no-cleanup test/.buildignore; +touch --no-cleanup test/dummy; $* <>/EOE ./: testscript{foo} EOI diff --git a/tests/test/script/runner/redirect.testscript b/tests/test/script/runner/redirect.testscript index 21e9d07..7e4e42d 100644 --- a/tests/test/script/runner/redirect.testscript +++ b/tests/test/script/runner/redirect.testscript @@ -43,12 +43,14 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. $c <'$* -o foo >!' && $b >foo 2>>/~%EOE% %test .+% mkdir test/ + touch test/.buildignore cd test/ mkdir test/1/ cd test/1/ %.*/driver(.exe)? -o foo% rmdir test/1/ cd test/ + rm test/.buildignore rmdir test/ cd ./ EOE @@ -58,6 +60,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. $c <'$* -e foo 2>!' && $b 2>>/~%EOE% %test .+% mkdir test/ + touch test/.buildignore cd test/ mkdir test/1/ cd test/1/ @@ -65,6 +68,7 @@ psr = ($cxx.target.class != 'windows' ? '/' : '\\') # Path separator in regex. foo rmdir test/1/ cd test/ + rm test/.buildignore rmdir test/ cd ./ EOE -- cgit v1.1