aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build2/filesystem.cxx59
-rw-r--r--build2/filesystem.hxx22
-rw-r--r--build2/parser.cxx3
-rw-r--r--build2/test/rule.cxx14
-rw-r--r--build2/test/script/runner.cxx26
-rw-r--r--tests/name/pattern.testscript15
-rw-r--r--tests/test/script/integration/testscript8
-rw-r--r--tests/test/script/runner/redirect.testscript4
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_status>
+ mkdir_buildignore (const dir_path& d, uint16_t verbosity)
+ {
+ fs_status<mkdir_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_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_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_status>
+ rmdir_buildignore (const dir_path&, uint16_t verbosity = 1);
}
#include <build2/filesystem.txx>
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 <build2/context.hxx>
#include <build2/function.hxx>
#include <build2/variable.hxx>
+#include <build2/filesystem.hxx>
#include <build2/diagnostics.hxx>
#include <build2/prerequisite.hxx>
@@ -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<mkdir_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<dir_path> (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 <uint16_t> (v)));
+ rmdir_status r (recursive
+ ? rmdir_r (d, !wd, static_cast <uint16_t> (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;
$* <<EOI 2>>/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;
$* <<EOI 2>>/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