From c2b4305349ca855c497904282db354de56c74842 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 7 Aug 2018 14:59:07 +0200 Subject: 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) --- build2/algorithm.cxx | 10 ++- build2/b.cxx | 14 ++-- build2/cc/common.cxx | 8 +- build2/config/operation.cxx | 4 +- build2/in/target.cxx | 9 +- build2/parser.cxx | 200 +++++++++++++++++++++++++++++--------------- build2/scope.cxx | 61 +++++++------- build2/scope.hxx | 8 +- build2/target-type.hxx | 11 ++- build2/target.cxx | 142 ++++++++++++++++++++++++++----- build2/target.hxx | 23 ++++- build2/target.txx | 65 +++++++------- build2/test/target.cxx | 19 +++-- tests/name/extension.test | 71 ++++++++++++++++ 14 files changed, 457 insertions(+), 188 deletions(-) create mode 100644 tests/name/extension.test 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 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& 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 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& 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 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& 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 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& 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..65281a1 100644 --- a/build2/in/target.cxx +++ b/build2/in/target.cxx @@ -34,9 +34,14 @@ namespace build2 } static bool - in_pattern (const target_type&, const scope&, string&, bool) + in_pattern (const target_type&, + const scope&, + string&, + optional&, + const location& l, + bool) { - fail << "pattern in in{} prerequisite" << endf; + fail (l) << "pattern in in{} prerequisite" << endf; } extern const char in_ext_def[] = ""; // No extension by default. diff --git a/build2/parser.cxx b/build2/parser.cxx index 57c7fc9..d2cf38a 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -149,10 +149,9 @@ namespace build2 name& o, const location& loc) { - optional 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 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& 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&& 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&& 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&& 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 func; + function&&)> appf; if (unique) - func = [a, &append] (path&& m, const string& p, bool interm) + appf = [a, &append] (string&& v, optional&& 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&& 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 (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); @@ -3105,21 +3121,31 @@ namespace build2 // Amend the pattern or match in a target type-specific manner. // - bool a (tt != nullptr && - tt->pattern != nullptr && - tt->pattern (*tt, *scope_, v, 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. + // Name splitting must be consistent with scope::find_target_type(). + // Since we don't do it for directories, we have to delegate it to the + // target_type::pattern() call. // + bool a (false); // Amended. + optional e; // Extension. { - bool d (path::traits::is_separator (v.back ())); + bool d; + + if (tt != nullptr && tt->pattern != nullptr) + { + a = tt->pattern (*tt, *scope_, v, e, l, false); + d = path::traits::is_separator (v.back ()); + } + else + { + d = path::traits::is_separator (v.back ()); + if (!d) + e = target::split_name (v, l); + } + + // Based on the first pattern verify inclusions/exclusions are + // consistently file/directory. + // if (first) dir = d; else if (d != dir) @@ -3127,21 +3153,37 @@ namespace build2 << " pattern"; } + // Figure out if this is a pattern. + // + bool p (v.find_first_of ("*?") != string::npos); + assert (p || !first); // First must be a pattern. + + // Factor non-empty extension back into the name for searching. + // + // Note that doing it at this stage means we don't support extension + // patterns. + // + if (e && !e->empty ()) + { + v += '.'; + v += *e; + } + try { 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 +3193,50 @@ 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 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); } } + + bool de (false); // Default extension. + if ((n.pair & 0x01) != 0) + { + de = static_cast (e); + tt->pattern (*tt, *scope_, v, e, l, true); + de = de && !e; + } + + if (dir) + n.dir = dir_path (move (v)); + else + { + target::combine_name (v, e, de); + 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& ext, const location& loc) const + pair> scope:: + find_target_type (name& n, const location& loc) const { - ext = nullopt; + const target_type* tt (nullptr); + optional 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 () || r->is_a ())) + if (tt != nullptr && (tt->is_a () || tt->is_a ())) { // 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& ext, const location&) const; + pair> + 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..37e511d 100644 --- a/build2/target-type.hxx +++ b/build2/target-type.hxx @@ -45,7 +45,9 @@ namespace build2 // // If the pattern function is not NULL, then it is used to amend a pattern // or match (reverse is false) and then, if the amendment call returned - // true, to reverse it in the resulting matches. + // true, to reverse it in the resulting matches. The pattern function for a + // non-directory target must first call target::split_name() if reverse is + // false. // struct target_type { @@ -60,7 +62,12 @@ 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& extension, + const location&, + bool reverse); void (*print) (ostream&, const target_key&); diff --git a/build2/target.cxx b/build2/target.cxx index e6188cd..5d11337 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -192,6 +192,79 @@ namespace build2 return r; } + optional 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 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& e, bool de) + { + 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 (de) + { + if (path::traits::find_extension (v) != string::npos) + v += "..."; + } + } + // target_set // target_set targets; @@ -802,7 +875,12 @@ 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&, + const location&, + bool r) { // Add/strip trailing directory separator unless already there. // @@ -872,19 +950,27 @@ 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& e, + const location& l, + 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 { - v += ".exe"; - return true; + e = target::split_name (v, l); + + if (!e) + { + e = "exe"; + return true; + } } return false; @@ -921,19 +1007,24 @@ namespace build2 buildfile_target_pattern (const target_type&, const scope&, string& v, + optional& e, + const location& l, 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 { - v += ".build"; - return true; + e = target::split_name (v, l); + + if (!e && v != "buildfile") + { + e = "build"; + return true; + } } return false; @@ -1015,19 +1106,24 @@ namespace build2 manifest_target_pattern (const target_type&, const scope&, string& v, + optional& e, + const location& l, 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 { - v += ".manifest"; - return true; + e = target::split_name (v, l); + + if (!e && v != "manifest") + { + e = "manifest"; + return true; + } } return false; diff --git a/build2/target.hxx b/build2/target.hxx index db5de67..1249f5c 100644 --- a/build2/target.hxx +++ b/build2/target.hxx @@ -648,6 +648,21 @@ namespace build2 static const target_type static_type; public: + // Split the name leaf into target name (in place) and extension + // (returned). + // + static optional + split_name (string&, const location&); + + // Combine the target name and extension into the name leaf. + // + // If the target type has the default extension, then "escape" the + // existing extension if any. + // + static void + combine_name (string&, const optional&, bool default_extension); + + public: virtual ~target (); @@ -1704,7 +1719,9 @@ namespace build2 template bool - target_pattern_fix (const target_type&, const scope&, string&, bool); + target_pattern_fix (const target_type&, const scope&, + string&, optional&, const location&, + bool); // Get the extension from the variable or use the default if none set. If // the default is NULL, then return NULL. @@ -1715,7 +1732,9 @@ namespace build2 template bool - target_pattern_var (const target_type&, const scope&, string&, bool); + target_pattern_var (const target_type&, const scope&, + string&, optional&, const location&, + bool); // Always return NULL extension. // diff --git a/build2/target.txx b/build2/target.txx index d832d6b..a29a289 100644 --- a/build2/target.txx +++ b/build2/target.txx @@ -55,27 +55,30 @@ namespace build2 template bool - target_pattern_fix (const target_type&, const scope&, string& v, bool r) + target_pattern_fix (const target_type&, + const scope&, + string& v, + optional& e, + const location& l, + 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 (*ext != '\0') // Don't add empty extension (means no extension). + e = target::split_name (v, l); + + // We only add our extension if there isn't one already. + // + if (!e) { - v += '.'; - v += ext; + e = ext; return true; } } @@ -115,34 +118,34 @@ namespace build2 template 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& v, + optional& e, + const location& l, + 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 { - // Use empty name as a target since we only want target type/pattern- - // specific variables that match any target (e.g., '*' but not '*.txt'). + e = target::split_name (v, l); + + // We only add our extension if there isn't one already. // - if (auto e = target_extension_var_impl (tt, string (), s, var, def)) + if (!e) { - if (!e->empty ()) // Don't add empty extension (means no extension). - { - v += '.'; - v += *e; + // Use empty name as a target since we only want target type/pattern- + // specific variables that match any target ('*' but not '*.txt'). + // + if ((e = target_extension_var_impl (tt, string (), s, var, def))) return true; - } } } diff --git a/build2/test/target.cxx b/build2/test/target.cxx index a517240..25afd0d 100644 --- a/build2/test/target.cxx +++ b/build2/test/target.cxx @@ -24,19 +24,24 @@ namespace build2 testscript_target_pattern (const target_type&, const scope&, string& v, + optional& e, + const location& l, 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 { - v += ".test"; - return true; + e = target::split_name (v, l); + + if (!e && v != "testscript") + { + e = "test"; + return true; + } } return false; diff --git a/tests/name/extension.test b/tests/name/extension.test new file mode 100644 index 0000000..c7d7066 --- /dev/null +++ b/tests/name/extension.test @@ -0,0 +1,71 @@ +# file : tests/name/extension.test +# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +.include ../common.test + ++cat <=build/root.build +define txt: file +txt{*}: extension = txt +EOI + +: name +: +{ + test.arguments = --match-only update + + : 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 + :1:5: error: invalid trailing dot sequence in target name 'foo.....' + EOE +} + +: pattern +: +{ + : specified-none + : + touch foo; + $* <'print txt{fo?.}' >'txt{foo.}' + + : specified-default + : + touch foo.test.txt; + $* <'print txt{fo?.test...}' >'txt{foo.test...}' + + : specified-escape + : + touch foo.; + $* <'print txt{fo?..}' >'txt{foo..}' +} -- cgit v1.1