diff options
Diffstat (limited to 'libbuild2/functions-path.cxx')
-rw-r--r-- | libbuild2/functions-path.cxx | 708 |
1 files changed, 577 insertions, 131 deletions
diff --git a/libbuild2/functions-path.cxx b/libbuild2/functions-path.cxx index b79585d..2f7f159 100644 --- a/libbuild2/functions-path.cxx +++ b/libbuild2/functions-path.cxx @@ -154,14 +154,95 @@ namespace build2 return path_match (entry, pattern, *start); } + // Don't fail for absolute paths on Windows and, for example, just return + // c:/foo for c:\foo. + // + template <typename P> + static inline string + posix_string (P&& p) + { +#ifndef _WIN32 + return move (p).posix_string (); +#else + if (p.relative ()) + return move (p).posix_string (); + + // Note: also handles root directories. + // + dir_path d (p.root_directory ()); + return d.string () + '/' + p.leaf (d).posix_string (); +#endif + } + + // Similar to the above don't fail for absolute paths on Windows. + // + template <typename P> + static inline string + posix_representation (P&& p) + { +#ifndef _WIN32 + return move (p).posix_representation (); +#else + if (p.relative ()) + return move (p).posix_representation (); + + // Note: also handles root directories. + // + dir_path d (p.root_directory ()); + return d.string () + '/' + p.leaf (d).posix_representation (); +#endif + } + + template <typename P> + static bool + try_normalize (P& p) + { + try + { + p.normalize (); + return true; + } + catch (const invalid_path&) {} + + return false; + } + + template <typename P> + static bool + try_actualize (P& p) + { + try + { + p.normalize (true); + return true; + } + catch (const invalid_path&) {} + catch (const system_error&) {} + + return false; + } + void path_functions (function_map& m) { function_family f (m, "path", &path_thunk); - // string + // $string(<paths>) + // + // Return the traditional string representation of a path (or a list of + // string representations for a list of paths). In particular, for + // directory paths, the traditional representation does not include the + // trailing directory separator (except for the POSIX root directory). See + // `$representation()` below for the precise string representation. // - f["string"] += [](path p) {return move (p).string ();}; + + // Note that we must handle NULL values (relied upon by the parser + // to provide conversion semantics consistent with untyped values). + // + f["string"] += [](path* p) + { + return p != nullptr ? move (*p).string () : string (); + }; f["string"] += [](paths v) { @@ -179,7 +260,53 @@ namespace build2 return r; }; - // representation + // $posix_string(<paths>) + // $path.posix_string(<untyped>) + // + // Return the traditional string representation of a path (or a list of + // string representations for a list of paths) using the POSIX directory + // separators (forward slashes). + // + f["posix_string"] += [](path p) {return posix_string (move (p));}; + f["posix_string"] += [](dir_path p) {return posix_string (move (p));}; + + f["posix_string"] += [](paths v) + { + strings r; + for (auto& p: v) + r.push_back (posix_string (move (p))); + return r; + }; + + f["posix_string"] += [](dir_paths v) + { + strings r; + for (auto& p: v) + r.push_back (posix_string (move (p))); + return r; + }; + + f[".posix_string"] += [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of strings. + // + for (name& n: ns) + { + n = n.directory () + ? posix_string (move (n.dir)) + : posix_string (convert<path> (move (n))); + } + return ns; + }; + + // $representation(<paths>) + // + // Return the precise string representation of a path (or a list of string + // representations for a list of paths). In particular, for directory + // paths, the precise representation includes the trailing directory + // separator. See `$string()` above for the traditional string + // representation. // f["representation"] += [](path p) {return move (p).representation ();}; @@ -199,124 +326,131 @@ namespace build2 return r; }; - // canonicalize + // $posix_representation(<paths>) + // $path.posix_representation(<untyped>) // - // @@ TODO: add ability to specify alternative separator. + // Return the precise string representation of a path (or a list of string + // representations for a list of paths) using the POSIX directory + // separators (forward slashes). // - f["canonicalize"] += [](path p) {p.canonicalize (); return p;}; - f["canonicalize"] += [](dir_path p) {p.canonicalize (); return p;}; + f["posix_representation"] += [](path p) + { + return posix_representation (move (p)); + }; - f["canonicalize"] += [](paths v) + f["posix_representation"] += [](dir_path p) { + return posix_representation (move (p)); + }; + + f["posix_representation"] += [](paths v) + { + strings r; for (auto& p: v) - p.canonicalize (); - return v; + r.push_back (posix_representation (move (p))); + return r; }; - f["canonicalize"] += [](dir_paths v) + f["posix_representation"] += [](dir_paths v) { + strings r; for (auto& p: v) - p.canonicalize (); - return v; + r.push_back (posix_representation (move (p))); + return r; }; - f[".canonicalize"] += [](names ns) + f[".posix_representation"] += [](names ns) { // For each path decide based on the presence of a trailing slash - // whether it is a directory. Return as untyped list of (potentially - // mixed) paths. + // whether it is a directory. Return as untyped list of strings. // for (name& n: ns) { - if (n.directory ()) - n.dir.canonicalize (); - else - n.value = convert<path> (move (n)).canonicalize ().string (); + n = n.directory () + ? posix_representation (move (n.dir)) + : posix_representation (convert<path> (move (n))); } return ns; }; - // normalize + // $absolute(<path>) + // $path.absolute(<untyped>) // - f["normalize"] += [](path p) {p.normalize (); return p;}; - f["normalize"] += [](dir_path p) {p.normalize (); return p;}; + // Return true if the path is absolute and false otherwise. + // + f["absolute"] += [](path p) + { + return p.absolute (); + }; - f["normalize"] += [](paths v) + f[".absolute"] += [](names ns) { - for (auto& p: v) - p.normalize (); - return v; + return convert<path> (move (ns)).absolute (); }; - f["normalize"] += [](dir_paths v) + // $simple(<path>) + // $path.simple(<untyped>) + // + // Return true if the path is simple, that is, has no direcrory component, + // and false otherwise. + // + // Note that on POSIX `/foo` is not a simple path (it is `foo` in the root + // directory) while `/` is (it is the root directory). + // + f["simple"] += [](path p) { - for (auto& p: v) - p.normalize (); - return v; + return p.simple (); }; - f[".normalize"] += [](names ns) + f[".simple"] += [](names ns) { - // For each path decide based on the presence of a trailing slash - // whether it is a directory. Return as untyped list of (potentially - // mixed) paths. - // - for (name& n: ns) - { - if (n.directory ()) - n.dir.normalize (); - else - n.value = convert<path> (move (n)).normalize ().string (); - } - return ns; + return convert<path> (move (ns)).simple (); }; - // actualize + // $sub_path(<path>, <path>) + // $path.sub_path(<untyped>, <untyped>) // - // Note that this function is not pure. + // Return true if the path specified as the first argument is a sub-path + // of the one specified as the second argument (in other words, the second + // argument is a prefix of the first) and false otherwise. Both paths are + // expected to be normalized. Note that this function returns true if the + // paths are equal. Empty path is considered a prefix of any path. // + f["sub_path"] += [](path p, value v) { - auto e (f.insert ("actualize", false)); - - e += [](path p) {p.normalize (true); return p;}; - e += [](dir_path p) {p.normalize (true); return p;}; + return p.sub (convert_to_base<path> (move (v))); + }; - e += [](paths v) - { - for (auto& p: v) - p.normalize (true); - return v; - }; + f[".sub_path"] += [](names ns, value v) + { + return convert<path> (move (ns)).sub (convert_to_base<path> (move (v))); + }; - e += [](dir_paths v) - { - for (auto& p: v) - p.normalize (true); - return v; - }; - } + // $super_path(<path>, <path>) + // $path.super_path(<untyped>, <untyped>) + // + // Return true if the path specified as the first argument is a super-path + // of the one specified as the second argument (in other words, the second + // argument is a suffix of the first) and false otherwise. Both paths are + // expected to be normalized. Note that this function returns true if the + // paths are equal. Empty path is considered a suffix of any path. + // + f["super_path"] += [](path p, value v) + { + return p.sup (convert_to_base<path> (move (v))); + }; - f.insert (".actualize", false) += [](names ns) + f[".super_path"] += [](names ns, value v) { - // For each path decide based on the presence of a trailing slash - // whether it is a directory. Return as untyped list of (potentially - // mixed) paths. - // - for (name& n: ns) - { - if (n.directory ()) - n.dir.normalize (true); - else - n.value = convert<path> (move (n)).normalize (true).string (); - } - return ns; + return convert<path> (move (ns)).sup (convert_to_base<path> (move (v))); }; - // $directory(<path>) // $directory(<paths>) + // $path.directory(<untyped>) // - // Return the directory part of the path or empty path if there is no - // directory. Directory of a root directory is an empty path. + // Return the directory part of a path (or a list of directory parts for a + // list of paths) or an empty path if there is no directory. A directory of + // a root directory is an empty path. // f["directory"] += &path::directory; @@ -350,11 +484,12 @@ namespace build2 return ns; }; - // $root_directory(<path>) // $root_directory(<paths>) + // $path.root_directory(<untyped>) // - // Return the root directory of the path or empty path if the directory is - // not absolute. + // Return the root directory of a path (or a list of root directories for + // a list of paths) or an empty path if the specified path is not + // absolute. // f["root_directory"] += &path::root_directory; @@ -388,17 +523,22 @@ namespace build2 return ns; }; - // $leaf(<path>) - // - f["leaf"] += &path::leaf; - - // $leaf(<path>, <dir-path>) + // $leaf(<paths>) + // $path.leaf(<untyped>) // $leaf(<paths>, <dir-path>) + // $path.leaf(<untyped>, <dir-path>) + // + // First form (one argument): return the last component of a path (or a + // list of last components for a list of paths). // - // Return the path without the specified directory part. Return empty path - // if the paths are the same. Issue diagnostics and fail if the directory - // is not a prefix of the path. Note: expects both paths to be normalized. + // Second form (two arguments): return a path without the specified + // directory part (or a list of paths without the directory part for a + // list of paths). Return an empty path if the paths are the same. Issue + // diagnostics and fail if the directory is not a prefix of the + // path. Note: expects both paths to be normalized. // + f["leaf"] += &path::leaf; + f["leaf"] += [](path p, dir_path d) { return leaf (p, move (d)); @@ -434,13 +574,15 @@ namespace build2 return ns; }; - // $relative(<path>, <dir-path>) // $relative(<paths>, <dir-path>) + // $path.relative(<untyped>, <dir-path>) + // + // Return the path relative to the specified directory that is equivalent + // to the specified path (or a list of relative paths for a list of + // specified paths). Issue diagnostics and fail if a relative path cannot + // be derived (for example, paths are on different drives on Windows). // - // Return a path relative to the specified directory that is equivalent to - // the specified path. Issue diagnostics and fail if a relative path - // cannot be derived (for example, paths are on different drives on - // Windows). + // Note: to check if a path if relative, use `$path.absolute()`. // f["relative"] += [](path p, dir_path d) { @@ -477,7 +619,11 @@ namespace build2 return ns; }; - // base + // $base(<paths>) + // $path.base(<untyped>) + // + // Return the base part (without the extension) of a path (or a list of + // base parts for a list of paths). // f["base"] += &path::base; @@ -511,7 +657,11 @@ namespace build2 return ns; }; - // extension + // $extension(<path>) + // $path.extension(<untyped>) + // + // Return the extension part (without the dot) of a path or empty string + // if there is no extension. // f["extension"] += &extension; @@ -520,33 +670,285 @@ namespace build2 return extension (convert<path> (move (ns))); }; - // $size(<paths>) - // $size(<dir_paths>) + // $complete(<paths>) + // $path.complete(<untyped>) // - // Return the number of elements in the sequence. + // Complete the path (or list of paths) by prepending the current working + // directory unless the path is already absolute. // - f["size"] += [] (paths v) {return v.size ();}; - f["size"] += [] (dir_paths v) {return v.size ();}; + f["complete"] += [](path p) {p.complete (); return p;}; + f["complete"] += [](dir_path p) {p.complete (); return p;}; + + f["complete"] += [](paths v) + { + for (auto& p: v) + p.complete (); + return v; + }; + + f["complete"] += [](dir_paths v) + { + for (auto& p: v) + p.complete (); + return v; + }; + f[".complete"] += [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of (potentially + // mixed) paths. + // + for (name& n: ns) + { + if (n.directory ()) + n.dir.complete (); + else + n.value = convert<path> (move (n)).complete ().string (); + } + return ns; + }; + + // $canonicalize(<paths>) + // $path.canonicalize(<untyped>) + // + // Canonicalize the path (or list of paths) by converting all the + // directory separators to the canonical form for the host platform. Note + // that multiple directory separators are not collapsed. + // + f["canonicalize"] += [](path p) {p.canonicalize (); return p;}; + f["canonicalize"] += [](dir_path p) {p.canonicalize (); return p;}; + + f["canonicalize"] += [](paths v) + { + for (auto& p: v) + p.canonicalize (); + return v; + }; + + f["canonicalize"] += [](dir_paths v) + { + for (auto& p: v) + p.canonicalize (); + return v; + }; + + f[".canonicalize"] += [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of (potentially + // mixed) paths. + // + for (name& n: ns) + { + if (n.directory ()) + n.dir.canonicalize (); + else + n.value = convert<path> (move (n)).canonicalize ().string (); + } + return ns; + }; + + // $normalize(<paths>) + // $path.normalize(<untyped>) + // $try_normalize(<path>) + // $path.try_normalize(<untyped>) + // + // Normalize the path (or list of paths) by collapsing the `.` and `..` + // components if possible, collapsing multiple directory separators, and + // converting all the directory separators to the canonical form for the + // host platform. + // + // If the resulting path would be invalid, the `$normalize()` version + // issues diagnostics and fails while the `$try_normalize()` version + // returns `null`. Note that `$try_normalize()` only accepts a single + // path. + // + f["normalize"] += [](path p) {p.normalize (); return p;}; + f["normalize"] += [](dir_path p) {p.normalize (); return p;}; + + f["normalize"] += [](paths v) + { + for (auto& p: v) + p.normalize (); + return v; + }; + + f["normalize"] += [](dir_paths v) + { + for (auto& p: v) + p.normalize (); + return v; + }; + + f[".normalize"] += [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of (potentially + // mixed) paths. + // + for (name& n: ns) + { + if (n.directory ()) + n.dir.normalize (); + else + n.value = convert<path> (move (n)).normalize ().string (); + } + return ns; + }; + + f["try_normalize"] += [](path p) + { + return try_normalize (p) ? value (move (p)) : value (nullptr); + }; + + f["try_normalize"] += [](dir_path p) + { + return try_normalize (p) ? value (move (p)) : value (nullptr); + }; + + f[".try_normalize"] += [](names ns) + { + if (ns.size () != 1) + throw invalid_argument ("multiple paths"); + + name& n (ns.front ()); + + bool r; + if (n.directory ()) + r = try_normalize (n.dir); + else + { + path p (convert<path> (move (n))); + if ((r = try_normalize (p))) + n.value = move (p).string (); + } + + return r ? value (move (ns)) : value (nullptr); + }; + + // $actualize(<paths>) + // $path.actualize(<untyped>) + // $try_actualize(<path>) + // $path.try_actualize(<untyped>) + // + // Actualize the path (or list of paths) by first normalizing it and then + // for host platforms with case-insensitive filesystems obtaining the + // actual spelling of the path. + // + // Only an absolute path can be actualized. If a path component does not + // exist, then its (and all subsequent) spelling is unchanged. Note that + // this is a potentially expensive operation. + // + // If the resulting path would be invalid or in case of filesystem errors + // (other than non-existent component), the `$actualize()` version issues + // diagnostics and fails while the `$try_actualize()` version returns + // `null`. Note that `$try_actualize()` only accepts a single path. + // + // Note that this function is not pure. + // + { + auto e (f.insert ("actualize", false)); + + e += [](path p) {p.normalize (true); return p;}; + e += [](dir_path p) {p.normalize (true); return p;}; + + e += [](paths v) + { + for (auto& p: v) + p.normalize (true); + return v; + }; + + e += [](dir_paths v) + { + for (auto& p: v) + p.normalize (true); + return v; + }; + } + + f.insert (".actualize", false) += [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of (potentially + // mixed) paths. + // + for (name& n: ns) + { + if (n.directory ()) + n.dir.normalize (true); + else + n.value = convert<path> (move (n)).normalize (true).string (); + } + return ns; + }; + + { + auto e (f.insert ("try_actualize", false)); + + e += [](path p) + { + return try_actualize (p) ? value (move (p)) : value (nullptr); + }; + + e += [](dir_path p) + { + return try_actualize (p) ? value (move (p)) : value (nullptr); + }; + } + + f.insert (".try_actualize", false) += [](names ns) + { + if (ns.size () != 1) + throw invalid_argument ("multiple paths"); + + name& n (ns.front ()); + + bool r; + if (n.directory ()) + r = try_actualize (n.dir); + else + { + path p (convert<path> (move (n))); + if ((r = try_actualize (p))) + n.value = move (p).string (); + } + + return r ? value (move (ns)) : value (nullptr); + }; + + + // Note that we currently do not expose realize(). For one, it might be + // tricky to handle CWD overrides (on POSIX we just call realize(3)). + // Also, our implementation for Windows currently does not handle + // symlinks. + + + // $size(<paths>) // $size(<path>) - // $size(<dir_path>) // - // Return the number of characters (bytes) in the path. Note that for - // dir_path the result does not include the trailing directory separator - // (except for the POSIX root directory). + // First form: return the number of elements in the paths sequence. + // + // Second form: return the number of characters (bytes) in the path. Note + // that for `dir_path` the result does not include the trailing directory + // separator (except for the POSIX root directory). + // // + f["size"] += [] (paths v) {return v.size ();}; + f["size"] += [] (dir_paths v) {return v.size ();}; + f["size"] += [] (path v) {return v.size ();}; f["size"] += [] (dir_path v) {return v.size ();}; - // $sort(<paths> [, <flags>]) - // $sort(<dir_paths> [, <flags>]) + // $sort(<paths>[, <flags>]) // - // Sort paths in ascending order. Note that on case-insensitive filesystem - // the order is case-insensitive. + // Sort paths in ascending order. Note that on host platforms with a + // case-insensitive filesystem the order is case-insensitive. // // The following flags are supported: // - // dedup - in addition to sorting also remove duplicates + // dedup - in addition to sorting also remove duplicates // f["sort"] += [](paths v, optional<names> fs) { @@ -568,34 +970,73 @@ namespace build2 return v; }; - // $path.match(<val>, <pat> [, <start>]) + // $find(<paths>, <path>) + // + // Return true if the paths sequence contains the specified path. Note + // that on host platforms with a case-insensitive filesystem the + // comparison is case-insensitive. + // + f["find"] += [](paths vs, value v) + { + return find (vs.begin (), vs.end (), + convert<path> (move (v))) != vs.end (); + }; + + f["find"] += [](dir_paths vs, value v) + { + return find (vs.begin (), vs.end (), + convert<dir_path> (move (v))) != vs.end (); + }; + + // $find_index(<paths>, <path>) // - // Match a filesystem entry name against a name pattern (both are strings), - // or a filesystem entry path against a path pattern. For the latter case - // the start directory may also be required (see below). The semantics of - // the pattern and name/entry arguments is determined according to the + // Return the index of the first element in the paths sequence that is + // equal to the specified path or `$size(paths)` if none is found. Note + // that on host platforms with a case-insensitive filesystem the + // comparison is case-insensitive. + // + f["find_index"] += [](paths vs, value v) + { + auto i (find (vs.begin (), vs.end (), convert<path> (move (v)))); + return i != vs.end () ? i - vs.begin () : vs.size (); + }; + + f["find_index"] += [](dir_paths vs, value v) + { + auto i (find (vs.begin (), vs.end (), convert<dir_path> (move (v)))); + return i != vs.end () ? i - vs.begin () : vs.size (); + }; + + // $path.match(<entry>, <pattern>[, <start-dir>]) + // + // Match a filesystem entry name against a name pattern (both are + // strings), or a filesystem entry path against a path pattern. For the + // latter case the start directory may also be required (see below). The + // pattern is a shell-like wildcard pattern. The semantics of the + // <pattern> and <entry> arguments is determined according to the // following rules: // - // - The arguments must be of the string or path types, or be untyped. + // 1. The arguments must be of the string or path types, or be untyped. // - // - If one of the arguments is typed, then the other one must be of the - // same type or be untyped. In the later case, an untyped argument is - // converted to the type of the other argument. + // 2. If one of the arguments is typed, then the other one must be of the + // same type or be untyped. In the later case, an untyped argument is + // converted to the type of the other argument. // - // - If both arguments are untyped and the start directory is specified, - // then the arguments are converted to the path type. + // 3. If both arguments are untyped and the start directory is specified, + // then the arguments are converted to the path type. // - // - If both arguments are untyped and the start directory is not - // specified, then, if one of the arguments is syntactically a path (the - // value contains a directory separator), convert them to the path type, - // otherwise to the string type (match as names). + // 4. If both arguments are untyped and the start directory is not + // specified, then, if one of the arguments is syntactically a path (the + // value contains a directory separator), then they are converted to the + // path type, otherwise -- to the string type (match as names). // - // If pattern and entry paths are both either absolute or relative and - // non-empty, and the first pattern component is not a self-matching - // wildcard (doesn't contain ***), then the start directory is not - // required, and is ignored if specified. Otherwise, the start directory - // must be specified and be an absolute path. + // If pattern and entry paths are both either absolute or relative and not + // empty, and the first pattern component is not a self-matching wildcard + // (doesn't contain `***`), then the start directory is not required, and + // is ignored if specified. Otherwise, the start directory must be + // specified and be an absolute path. // + // Name matching. // f[".match"] += [](string name, string pattern) @@ -655,6 +1096,11 @@ namespace build2 // function_family b (m, "builtin", &path_thunk); + // Note that while we should normally handle NULL values (relied upon by + // the parser to provide concatenation semantics consistent with untyped + // values), the result will unlikely be what the user expected, especially + // if the NULL value is on the LHS. So for now we keep it a bit tighter. + // b[".concat"] += &concat_path_string; b[".concat"] += &concat_dir_path_string; |