aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2018-08-07 14:59:07 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2018-08-07 14:59:07 +0200
commite4f41c7319261b9585bd501256664679457e1d9d (patch)
tree21ef10a30d932a3172080de2690c6e38d851d0e2
parent7149c8eaeec3efcfc9da0f89c9ae979ff2c07fd5 (diff)
Add support for default extension specification, trailing dot escaping
For example: cxx{*}: extension = cxx cxx{foo} # foo.cxx cxx{foo.test} # foo.test (probably what we want...) cxx{foo.test...} # foo.test.cxx (... is this) cxx{foo..} # foo. cxx{foo....} # foo.. cxx{foo.....} # error (must come in escape pair)
-rw-r--r--build2/algorithm.cxx10
-rw-r--r--build2/b.cxx14
-rw-r--r--build2/cc/common.cxx8
-rw-r--r--build2/config/operation.cxx4
-rw-r--r--build2/in/target.cxx6
-rw-r--r--build2/parser.cxx187
-rw-r--r--build2/scope.cxx61
-rw-r--r--build2/scope.hxx8
-rw-r--r--build2/target-type.hxx6
-rw-r--r--build2/target.cxx117
-rw-r--r--build2/target.hxx20
-rw-r--r--build2/target.txx51
-rw-r--r--build2/test/target.cxx11
-rw-r--r--tests/name/extension.test48
14 files changed, 372 insertions, 179 deletions
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index 550a491..bfe6943 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -49,8 +49,9 @@ namespace build2
{
assert (phase == run_phase::match);
- optional<string> ext;
- const target_type* tt (s.find_target_type (n, ext, location ()));
+ auto rp (s.find_target_type (n, location ()));
+ const target_type* tt (rp.first);
+ optional<string>& ext (rp.second);
if (tt == nullptr)
fail << "unknown target type " << n.type << " in name " << n;
@@ -77,8 +78,9 @@ namespace build2
assert (phase == run_phase::match || phase == run_phase::execute);
name n (cn);
- optional<string> ext;
- const target_type* tt (s.find_target_type (n, ext, location ()));
+ auto rp (s.find_target_type (n, location ()));
+ const target_type* tt (rp.first);
+ optional<string>& ext (rp.second);
// For now we treat an unknown target type as an unknown target. Seems
// logical.
diff --git a/build2/b.cxx b/build2/b.cxx
index de4ee0c..aedda7f 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -694,8 +694,9 @@ main (int argc, char* argv[])
const string& v (tn.value);
// Handle a few common cases as special: empty name, '.', '..', as
- // well as dir{foo/bar} (without trailing '/'). This code must be
- // consistent with find_target_type() and other places.
+ // well as dir{foo/bar} (without trailing '/'). This logic must be
+ // consistent with find_target_type() and other places (grep for
+ // "..").
//
if (v.empty () || v == "." || v == ".." || tn.type == "dir")
out_base = dir_path (v);
@@ -1274,10 +1275,11 @@ main (int argc, char* argv[])
// Find the target type and extract the extension.
//
- optional<string> e;
- const target_type* ti (bs.find_target_type (tn, e, l));
+ auto rp (bs.find_target_type (tn, l));
+ const target_type* tt (rp.first);
+ optional<string>& e (rp.second);
- if (ti == nullptr)
+ if (tt == nullptr)
fail (l) << "unknown target type " << tn.type;
if (mif->search != nullptr)
@@ -1303,7 +1305,7 @@ main (int argc, char* argv[])
mif->search (mparams,
rs, bs,
- target_key {ti, &d, &out, &tn.value, e},
+ target_key {tt, &d, &out, &tn.value, e},
l,
tgs);
}
diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx
index ca73483..c6ef3bd 100644
--- a/build2/cc/common.cxx
+++ b/build2/cc/common.cxx
@@ -431,11 +431,9 @@ namespace build2
{
// This is import.
//
- optional<string> ext;
-
- // Changes name.
- //
- const target_type* tt (s.find_target_type (n, ext, location ()));
+ auto rp (s.find_target_type (n, location ())); // Note: changes name.
+ const target_type* tt (rp.first);
+ optional<string>& ext (rp.second);
if (tt == nullptr)
fail << "unknown target type '" << n.type << "' in library " << n;
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
index f9e9253..accae17 100644
--- a/build2/config/operation.cxx
+++ b/build2/config/operation.cxx
@@ -931,8 +931,8 @@ namespace build2
{
const name& tn (ts.name);
- // Figure out the project directory. This code must be consistent
- // with find_target_type() and other places.
+ // Figure out the project directory. This logic must be consistent
+ // with find_target_type() and other places (grep for "..").
//
dir_path d;
diff --git a/build2/in/target.cxx b/build2/in/target.cxx
index fec1135..4bc9d6a 100644
--- a/build2/in/target.cxx
+++ b/build2/in/target.cxx
@@ -34,7 +34,11 @@ namespace build2
}
static bool
- in_pattern (const target_type&, const scope&, string&, bool)
+ in_pattern (const target_type&,
+ const scope&,
+ string&,
+ optional<string>&,
+ bool)
{
fail << "pattern in in{} prerequisite" << endf;
}
diff --git a/build2/parser.cxx b/build2/parser.cxx
index 57c7fc9..25742d0 100644
--- a/build2/parser.cxx
+++ b/build2/parser.cxx
@@ -149,10 +149,9 @@ namespace build2
name& o,
const location& loc)
{
- optional<string> e;
- const target_type* ti (p.scope_->find_target_type (n, e, loc));
+ auto r (p.scope_->find_target_type (n, loc));
- if (ti == nullptr)
+ if (r.first == nullptr)
p.fail (loc) << "unknown target type " << n.type;
bool src (n.pair); // If out-qualified, then it is from src.
@@ -187,7 +186,7 @@ namespace build2
}
o.dir = move (out); // Result.
- return make_pair (ti, move (e));
+ return r;
}
~enter_target ()
@@ -740,10 +739,11 @@ namespace build2
//
for (auto& pn: pns)
{
- optional<string> e;
- const target_type* ti (scope_->find_target_type (pn, e, ploc));
+ auto rp (scope_->find_target_type (pn, ploc));
+ const target_type* tt (rp.first);
+ optional<string>& e (rp.second);
- if (ti == nullptr)
+ if (tt == nullptr)
fail (ploc) << "unknown target type " << pn.type;
// Current dir collapses to an empty one.
@@ -762,7 +762,7 @@ namespace build2
// path.
//
prerequisite p (pn.proj,
- *ti,
+ *tt,
move (pn.dir),
dir_path (),
move (pn.value),
@@ -2955,16 +2955,29 @@ namespace build2
return butl::path_match (pattern, p, *sp);
};
- // Append string to result according to dir. Store an indication of
- // whether it was amended in the pair flag.
+ // Append name/extension to result according to dir. Store an indication
+ // of whether it was amended as well as whether the extension is present
+ // in the pair flag. The extension itself is stored in name::type.
//
- auto append = [&r, &dir] (string&& v, bool a)
+ auto append = [&r, &dir] (string&& v, optional<string>&& e, bool a)
{
- r.push_back (dir ? name (dir_path (move (v))) : name (move (v)));
- r.back ().pair = a ? 'a' : '\0';
+ name n (dir ? name (dir_path (move (v))) : name (move (v)));
+
+ if (a)
+ n.pair |= 0x01;
+
+ if (e)
+ {
+ n.type = move (*e);
+ n.pair |= 0x02;
+ }
+
+ r.push_back (move (n));
};
- auto include_match = [&r, &equal, &append] (string&& m, bool a)
+ auto include_match = [&r, &equal, &append] (string&& m,
+ optional<string>&& e,
+ bool a)
{
auto i (find_if (
r.begin (),
@@ -2972,11 +2985,13 @@ namespace build2
[&m, &equal] (const name& n) {return equal (m, n);}));
if (i == r.end ())
- append (move (m), a);
+ append (move (m), move (e), a);
};
auto include_pattern =
- [&r, &append, &include_match, sp, &l, this] (string&& p, bool a)
+ [&r, &append, &include_match, sp, &l, this] (string&& p,
+ optional<string>&& e,
+ bool a)
{
// If we don't already have any matches and our pattern doesn't contain
// multiple recursive wildcards, then the result will be unique and we
@@ -2990,38 +3005,39 @@ namespace build2
unique = (i == string::npos || p.find ("**", i + 2) == string::npos);
}
- function<bool (path&&, const string&, bool)> func;
+ function<void (string&&, optional<string>&&)> appf;
if (unique)
- func = [a, &append] (path&& m, const string& p, bool interm)
+ appf = [a, &append] (string&& v, optional<string>&& e)
{
- // Ignore entries that start with a dot unless the pattern that
- // matched them also starts with a dot.
- //
- const string& s (m.string ());
- if (p[0] != '.' && s[path::traits::find_leaf (s)] == '.')
- return !interm;
-
- if (!interm)
- append (move (m).representation (), a);
-
- return true;
+ append (move (v), move (e), a);
};
else
- func = [a, &include_match] (path&& m, const string& p, bool interm)
+ appf = [a, &include_match] (string&& v, optional<string>&& e)
{
- const string& s (m.string ());
- if (p[0] != '.' && s[path::traits::find_leaf (s)] == '.')
- return !interm;
+ include_match (move (v), move (e), a);
+ };
- if (!interm)
- include_match (move (m).representation (), a);
+ auto process = [&e, &appf] (path&& m, const string& p, bool interm)
+ {
+ // Ignore entries that start with a dot unless the pattern that
+ // matched them also starts with a dot.
+ //
+ const string& s (m.string ());
+ if (p[0] != '.' && s[path::traits::find_leaf (s)] == '.')
+ return !interm;
- return true;
- };
+ // Note that we have to make copies of the extension since there will
+ // multiple entries for each pattern.
+ //
+ if (!interm)
+ appf (move (m).representation (), optional<string> (e));
+
+ return true;
+ };
try
{
- butl::path_search (path (move (p)), func, *sp);
+ butl::path_search (path (move (p)), process, *sp);
}
catch (const system_error& e)
{
@@ -3058,7 +3074,7 @@ namespace build2
// Process the pattern and inclusions/exclusions.
//
- for (auto b (pat.begin ()), i (b), e (pat.end ()); i != e; ++i)
+ for (auto b (pat.begin ()), i (b), end (pat.end ()); i != end; ++i)
{
name& n (*i);
bool first (i == b);
@@ -3086,7 +3102,30 @@ namespace build2
if (n.empty () || !(n.simple () || n.directory ()))
fail (l) << "invalid '" << n << "' in " << what << " pattern";
- string v (n.simple () ? move (n.value) : move (n.dir).representation ());
+ // Name splitting must be consistent with scope::find_target_type().
+ //
+ string v;
+ optional<string> e;
+ {
+ // Based on the first pattern figure out if the result is a directory
+ // or a file. For inclusions/exclusions verify it is consistent.
+ //
+ bool d (n.directory ());
+
+ if (first)
+ dir = d;
+ else if (d != dir)
+ fail (l) << "inconsistent file/directory result in " << what
+ << " pattern";
+
+ if (d)
+ v = move (n.dir).representation ();
+ else
+ {
+ v = move (n.value);
+ e = target::split_name (v, l);
+ }
+ }
// Figure out if this is inclusion or exclusion.
//
@@ -3107,24 +3146,21 @@ namespace build2
//
bool a (tt != nullptr &&
tt->pattern != nullptr &&
- tt->pattern (*tt, *scope_, v, false));
+ tt->pattern (*tt, *scope_, v, e, false));
// Figure out if this is a pattern.
//
bool p (v.find_first_of ("*?") != string::npos);
assert (p || !first); // First must be a pattern.
- // Based on the first pattern figure out if the result is a directory or
- // a file. For inclusions/exclusions verify it is consistent.
+ // Factor non-empty extension back into the name.
//
+ // Note that we don't support extension patterns.
+ //
+ if (e && !e->empty ())
{
- bool d (path::traits::is_separator (v.back ()));
-
- if (first)
- dir = d;
- else if (d != dir)
- fail (l) << "inconsistent file/directory result in " << what
- << " pattern";
+ v += '.';
+ v += *e;
}
try
@@ -3132,16 +3168,16 @@ namespace build2
if (s == '+')
{
if (p)
- include_pattern (move (v), a);
+ include_pattern (move (v), move (e), a);
else
- include_match (move (v), a);
+ include_match (move (v), move (e), a);
}
else
{
if (p)
exclude_pattern (move (v));
else
- exclude_match (v);
+ exclude_match (move (v));
}
}
catch (const invalid_path& e)
@@ -3151,26 +3187,45 @@ namespace build2
}
}
- // Reverse target type-specific pattern/match amendments from the result.
- // Essentially: cxx{*} -> *.cxx -> foo.cxx -> cxx{foo}.
+ // Post-process the result: remove extension, reverse target type-specific
+ // pattern/match amendments (essentially: cxx{*} -> *.cxx -> foo.cxx ->
+ // cxx{foo}), and recombined the result.
//
- if (tt != nullptr && tt->pattern != nullptr)
+ for (name& n: r)
{
- for (name& n: r)
+ string v;
+ optional<string> e;
+
+ if (dir)
+ v = move (n.dir).representation ();
+ else
{
- if (n.pair)
- {
- string v (dir ? move (n.dir).representation () : move (n.value));
- tt->pattern (*tt, *scope_, v, true);
+ v = move (n.value);
- if (dir)
- n.dir = dir_path (move (v));
- else
- n.value = move (v);
+ if ((n.pair & 0x02) != 0)
+ {
+ e = move (n.type);
- n.pair = '\0';
+ // Remove non-empty extension from the name (it got to be there, see
+ // above).
+ //
+ if (!e->empty ())
+ v.resize (v.size () - e->size () - 1);
}
}
+
+ if ((n.pair & 0x01) != 0)
+ tt->pattern (*tt, *scope_, v, e, true);
+
+ if (dir)
+ n.dir = dir_path (move (v));
+ else
+ {
+ target::combine_name (v, e);
+ n.value = move (v);
+ }
+
+ n.pair = '\0';
}
return splice_names (
diff --git a/build2/scope.cxx b/build2/scope.cxx
index dc08925..bf83830 100644
--- a/build2/scope.cxx
+++ b/build2/scope.cxx
@@ -588,36 +588,38 @@ namespace build2
return nullptr;
}
- const target_type* scope::
- find_target_type (name& n, optional<string>& ext, const location& loc) const
+ pair<const target_type*, optional<string>> scope::
+ find_target_type (name& n, const location& loc) const
{
- ext = nullopt;
+ const target_type* tt (nullptr);
+ optional<string> ext;
+
string& v (n.value);
// If the target type is specified, resolve it and bail out if not found.
// Otherwise, we know in the end it will resolve to something (if nothing
// else, either dir{} or file{}), so we can go ahead and process the name.
//
- const target_type* r (nullptr);
if (n.typed ())
{
- r = find_target_type (n.type);
+ tt = find_target_type (n.type);
- if (r == nullptr)
- return r;
+ if (tt == nullptr)
+ return make_pair (tt, move (ext));
}
else
{
- // Empty name as well as '.' and '..' signify a directory.
+ // Empty name as well as '.' and '..' signify a directory. Note that
+ // this logic must be consistent with other places (grep for "..").
//
if (v.empty () || v == "." || v == "..")
- r = &dir::static_type;
+ tt = &dir::static_type;
}
// Directories require special name processing. If we find that more
- // targets deviate, then we should make this target-type-specific.
+ // targets deviate, then we should make this target type-specific.
//
- if (r != nullptr && (r->is_a<dir> () || r->is_a<fsdir> ()))
+ if (tt != nullptr && (tt->is_a<dir> () || tt->is_a<fsdir> ()))
{
// The canonical representation of a directory name is with empty
// value.
@@ -634,50 +636,43 @@ namespace build2
// the extension (if any). We cannot assume the name part is a valid
// filesystem name so we will have to do the splitting manually.
//
- path::size_type i (path::traits::rfind_separator (v));
+ // See also parser::expand_name_pattern() if changing anything here.
+ //
+ size_t p (path::traits::rfind_separator (v));
- if (i != string::npos)
+ if (p != string::npos)
{
try
{
- n.dir /= dir_path (v, i != 0 ? i : 1); // Special case: "/".
+ n.dir /= dir_path (v, p != 0 ? p : 1); // Special case: "/".
}
catch (const invalid_path& e)
{
fail (loc) << "invalid path '" << e.path << "'";
}
- v = string (v, i + 1, string::npos);
+ v.erase (0, p + 1);
}
- // Extract the extension. Treat trailing dot as specified but empty
- // extension.
+ // Extract the extension.
//
- string::size_type j (v.back () != '.'
- ? path::traits::find_extension (v)
- : v.size () - 1);
-
- if (j != string::npos)
- {
- ext = string (v.c_str () + j + 1);
- v.resize (j);
- }
+ ext = target::split_name (v, loc);
}
// If the target type is still unknown, map it using the name/extension,
// falling back to file{}.
//
- if (r == nullptr)
+ if (tt == nullptr)
{
// We only consider files without extension for file name mapping.
//
if (!ext)
- r = find_file_target_type (this, v);
+ tt = find_file_target_type (this, v);
//@@ TODO: derive type from extension.
- if (r == nullptr)
- r = &file::static_type;
+ if (tt == nullptr)
+ tt = &file::static_type;
}
// If the target type does not use extensions but one was specified,
@@ -685,15 +680,15 @@ namespace build2
// diagnostics; see to_stream(target_key) for details).
//
if (ext &&
- r->fixed_extension == nullptr &&
- r->default_extension == nullptr)
+ tt->fixed_extension == nullptr &&
+ tt->default_extension == nullptr)
{
v += '.';
v += *ext;
ext = nullopt;
}
- return r;
+ return make_pair (tt, move (ext));
}
static target*
diff --git a/build2/scope.hxx b/build2/scope.hxx
index 73d95b6..d40d14a 100644
--- a/build2/scope.hxx
+++ b/build2/scope.hxx
@@ -213,12 +213,12 @@ namespace build2
// Given a name, figure out its type, taking into account extensions,
// special names (e.g., '.' and '..'), or anything else that might be
- // relevant. Also process the name (in place) by extracting the
- // extension, adjusting dir/value, etc., (note that the dir is not
+ // relevant. Process the name (in place) by extracting (and returning)
+ // extension, adjusting dir/leaf, etc., (note that the dir is not
// necessarily normalized). Return NULL if not found.
//
- const target_type*
- find_target_type (name&, optional<string>& ext, const location&) const;
+ pair<const target_type*, optional<string>>
+ find_target_type (name&, const location&) const;
// Dynamically derive a new target type from an existing one. Return the
// reference to the target type and an indicator of whether it was
diff --git a/build2/target-type.hxx b/build2/target-type.hxx
index 408a520..5027823 100644
--- a/build2/target-type.hxx
+++ b/build2/target-type.hxx
@@ -60,7 +60,11 @@ namespace build2
const char*,
bool search);
- bool (*pattern) (const target_type&, const scope&, string&, bool reverse);
+ bool (*pattern) (const target_type&,
+ const scope&,
+ string& name,
+ optional<string>& extension,
+ bool reverse);
void (*print) (ostream&, const target_key&);
diff --git a/build2/target.cxx b/build2/target.cxx
index e6188cd..ebc21db 100644
--- a/build2/target.cxx
+++ b/build2/target.cxx
@@ -192,6 +192,79 @@ namespace build2
return r;
}
+ optional<string> target::
+ split_name (string& v, const location& loc)
+ {
+ // We treat a single trailing dot as "specified no extension", double dots
+ // as a single trailing dot (that is, an escape sequence which can be
+ // repeated any number of times; in such cases we naturally assume there
+ // is no default extension) and triple dots as "unspecified (default)
+ // extension" (used when the extension in the name is not "ours", for
+ // example, cxx{foo.test...} for foo.test.cxx). An odd number of dots
+ // other than one or three is invalid.
+ //
+ optional<string> r;
+
+ size_t p;
+ if (v.back () != '.')
+ {
+ if ((p = path::traits::find_extension (v)) != string::npos)
+ r = string (v.c_str () + p + 1);
+ }
+ else
+ {
+ if ((p = v.find_last_not_of ('.')) == string::npos)
+ fail (loc) << "invalid target name '" << v << "'";
+
+ p++; // Position of the first trailing dot.
+ size_t n (v.size () - p); // Number of the trailing dots.
+
+ if (n == 1)
+ r = string ();
+ else if (n == 3)
+ ;
+ else if (n % 2 == 0)
+ {
+ p += n / 2; // Keep half of the dots.
+ r = string ();
+ }
+ else
+ fail (loc) << "invalid trailing dot sequence in target name '"
+ << v << "'";
+ }
+
+ if (p != string::npos)
+ v.resize (p);
+
+ return r;
+ }
+
+ void target::
+ combine_name (string& v, const optional<string>& e)
+ {
+ if (v.back () == '.')
+ {
+ assert (e && e->empty ());
+
+ size_t p (v.find_last_not_of ('.'));
+ assert (p != string::npos);
+
+ p++; // Position of the first trailing dot.
+ size_t n (v.size () - p); // Number of the trailing dots.
+ v.append (n, '.'); // Double them.
+ }
+ else if (e)
+ {
+ v += '.';
+ v += *e; // Empty or not.
+ }
+ else
+ {
+ if (path::traits::find_extension (v) != string::npos)
+ v += "...";
+ }
+ }
+
// target_set
//
target_set targets;
@@ -802,7 +875,11 @@ namespace build2
}
static bool
- dir_pattern (const target_type&, const scope&, string& v, bool r)
+ dir_pattern (const target_type&,
+ const scope&,
+ string& v,
+ optional<string>&,
+ bool r)
{
// Add/strip trailing directory separator unless already there.
//
@@ -872,18 +949,20 @@ namespace build2
#ifdef _WIN32
static bool
- exe_target_pattern (const target_type&, const scope&, string& v, bool r)
+ exe_target_pattern (const target_type&,
+ const scope&,
+ string& v,
+ optional<string>& e,
+ bool r)
{
- size_t p (path::traits::find_extension (v));
-
if (r)
{
- assert (p != string::npos);
- v.resize (p);
+ assert (e);
+ e = nullopt;
}
- else if (p == string::npos)
+ else if (!e)
{
- v += ".exe";
+ e = "exe";
return true;
}
@@ -921,18 +1000,17 @@ namespace build2
buildfile_target_pattern (const target_type&,
const scope&,
string& v,
+ optional<string>& e,
bool r)
{
- size_t p (path::traits::find_extension (v));
-
if (r)
{
- assert (p != string::npos);
- v.resize (p);
+ assert (e);
+ e = nullopt;
}
- else if (p == string::npos && v != "buildfile")
+ else if (!e && v != "buildfile")
{
- v += ".build";
+ e = "build";
return true;
}
@@ -1015,18 +1093,17 @@ namespace build2
manifest_target_pattern (const target_type&,
const scope&,
string& v,
+ optional<string>& e,
bool r)
{
- size_t p (path::traits::find_extension (v));
-
if (r)
{
- assert (p != string::npos);
- v.resize (p);
+ assert (e);
+ e = nullopt;
}
- else if (p == string::npos && v != "manifest")
+ else if (!e && v != "manifest")
{
- v += ".manifest";
+ e = "manifest";
return true;
}
diff --git a/build2/target.hxx b/build2/target.hxx
index db5de67..127940a 100644
--- a/build2/target.hxx
+++ b/build2/target.hxx
@@ -648,6 +648,18 @@ namespace build2
static const target_type static_type;
public:
+ // Split the name leaf into target name (in place) and extension
+ // (returned).
+ //
+ static optional<string>
+ split_name (string&, const location&);
+
+ // Combine the target name and extension into the name leaf.
+ //
+ static void
+ combine_name (string&, const optional<string>&);
+
+ public:
virtual
~target ();
@@ -1704,7 +1716,9 @@ namespace build2
template <const char* ext>
bool
- target_pattern_fix (const target_type&, const scope&, string&, bool);
+ target_pattern_fix (const target_type&, const scope&,
+ string&, optional<string>&,
+ bool);
// Get the extension from the variable or use the default if none set. If
// the default is NULL, then return NULL.
@@ -1715,7 +1729,9 @@ namespace build2
template <const char* var, const char* def>
bool
- target_pattern_var (const target_type&, const scope&, string&, bool);
+ target_pattern_var (const target_type&, const scope&,
+ string&, optional<string>&,
+ bool);
// Always return NULL extension.
//
diff --git a/build2/target.txx b/build2/target.txx
index d832d6b..8030c1a 100644
--- a/build2/target.txx
+++ b/build2/target.txx
@@ -55,29 +55,27 @@ namespace build2
template <const char* ext>
bool
- target_pattern_fix (const target_type&, const scope&, string& v, bool r)
+ target_pattern_fix (const target_type&,
+ const scope&,
+ string&,
+ optional<string>& e,
+ bool r)
{
- size_t p (path::traits::find_extension (v));
-
if (r)
{
// If we get called to reverse then it means we've added the extension
- // in the first place. So simply strip it.
+ // in the first place.
//
- assert (p != string::npos);
- v.resize (p);
+ assert (e);
+ e = nullopt;
}
//
// We only add our extension if there isn't one already.
//
- else if (p == string::npos)
+ else if (!e)
{
- if (*ext != '\0') // Don't add empty extension (means no extension).
- {
- v += '.';
- v += ext;
- return true;
- }
+ e = ext;
+ return true;
}
return false;
@@ -115,35 +113,30 @@ namespace build2
template <const char* var, const char* def>
bool
- target_pattern_var (const target_type& tt, const scope& s, string& v, bool r)
+ target_pattern_var (const target_type& tt,
+ const scope& s,
+ string&,
+ optional<string>& e,
+ bool r)
{
- size_t p (path::traits::find_extension (v));
-
if (r)
{
// If we get called to reverse then it means we've added the extension
- // in the first place. So simply strip it.
+ // in the first place.
//
- assert (p != string::npos);
- v.resize (p);
+ assert (e);
+ e = nullopt;
}
//
// We only add our extension if there isn't one already.
//
- else if (p == string::npos)
+ else if (!e)
{
// Use empty name as a target since we only want target type/pattern-
// specific variables that match any target (e.g., '*' but not '*.txt').
//
- if (auto e = target_extension_var_impl (tt, string (), s, var, def))
- {
- if (!e->empty ()) // Don't add empty extension (means no extension).
- {
- v += '.';
- v += *e;
- return true;
- }
- }
+ if ((e = target_extension_var_impl (tt, string (), s, var, def)))
+ return true;
}
return false;
diff --git a/build2/test/target.cxx b/build2/test/target.cxx
index a517240..57e2cb5 100644
--- a/build2/test/target.cxx
+++ b/build2/test/target.cxx
@@ -24,18 +24,17 @@ namespace build2
testscript_target_pattern (const target_type&,
const scope&,
string& v,
+ optional<string>& e,
bool r)
{
- size_t p (path::traits::find_extension (v));
-
if (r)
{
- assert (p != string::npos);
- v.resize (p);
+ assert (e);
+ e = nullopt;
}
- else if (p == string::npos && v != "testscript")
+ else if (!e && v != "testscript")
{
- v += ".test";
+ e = "test";
return true;
}
diff --git a/tests/name/extension.test b/tests/name/extension.test
new file mode 100644
index 0000000..e97d0ab
--- /dev/null
+++ b/tests/name/extension.test
@@ -0,0 +1,48 @@
+# file : tests/name/extension.test
+# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = --match-only update
+
+.include ../common.test
+
++cat <<EOI >=build/root.build
+define txt: file
+txt{*}: extension = txt
+EOI
+
+: unspecified
+:
+touch foo.txt;
+$* <'./: txt{foo}'
+
+: specified
+:
+touch foo.text;
+$* <'./: txt{foo.text}'
+
+: specified-none
+:
+touch foo;
+$* <'./: txt{foo.}'
+
+: specified-default
+:
+touch foo.test.txt;
+$* <'./: txt{foo.test...}'
+
+: specified-escape-one
+:
+touch foo.;
+$* <'./: txt{foo..}'
+
+: specified-escape-two
+:
+touch foo..;
+$* <'./: txt{foo....}'
+
+: specified-invalid
+:
+$* <'./: txt{foo.....}' 2>>EOE != 0
+<stdin>:1:5: error: invalid trailing dot sequence in target name 'foo.....'
+EOE