diff --git a/README.md b/README.md index 5283c377..104b2481 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ C library. ```toml [dependencies] -openkal-llvm-runtime = "0.19.0" +openkal-llvm-runtime = "0.19.1" ``` > **Engine floor (mcpp 2026.11.2.1):** 0.17.0 is built over openkal-musl 0.26.0, @@ -330,6 +330,15 @@ grants against raw file truncation. The runtime implementation is unchanged. ## OpenKal 0.24 dependency group +Runtime 0.19.1 reads a name beneath a volume, `X:/...`, as the C library beneath +does: `X:` is the root name and `/` the root directory, so the working directory on +a system with volumes is absolute and `absolute`, `canonical` and `relative` keep +it (openkal-llvm-runtime#64). `X:` without a separator stays an ordinary name. +Both probes (`examples/cxx`, `examples/image-cxx`) state the decomposition, the +append and the working-directory results; without the change four of them fail on +Linux and the working-directory results fail on Windows. The dependency on +openkal-musl is the compatible range `^0.28.0`. + Runtime 0.19.0 selects openkal-musl 0.28.0 and its matching OpenKal 0.24 implementations. The C++ runtime surface and allocator feature selection remain unchanged. diff --git a/examples/cxx/src/main.cpp b/examples/cxx/src/main.cpp index 8ebf623b..c95e13c5 100644 --- a/examples/cxx/src/main.cpp +++ b/examples/cxx/src/main.cpp @@ -129,6 +129,32 @@ int main() { check(same, "a string grows past the allocator's own mappings"); } + // --- names beneath a volume (openkal-llvm-runtime#64) ------------------- + // + // The C library names a file on a system with volumes `D:/...` and takes + // `X:/` as a root. The same name is absolute to std::filesystem, and the + // working directory is absolute on every system. + { + namespace fs = std::filesystem; + const fs::path v = "D:/a/b.txt"; + check(v.is_absolute() && v.root_name() == "D:" && v.root_directory() == "/", "`D:/a/b.txt' has a root name and a root directory"); + check(v.relative_path() == "a/b.txt" && v.parent_path() == "D:/a" && v.filename() == "b.txt", "and decomposes beneath it"); + std::vector forward, backward; + for (const auto& e : v) forward.push_back(e.string()); + for (auto it = v.end(); it != v.begin();) backward.insert(backward.begin(), (--it)->string()); + check(forward == std::vector{"D:", "/", "a", "b.txt"} && backward == forward, "its elements are the same read either way"); + check(!fs::path("D:").is_absolute() && fs::path("D:").root_name().empty(), "`D:' without a separator is an ordinary name"); + check(!fs::path("D:x").is_absolute(), "`D:x' is relative"); + check((fs::path("x") / "D:/y") == "D:/y" && fs::path("x").append(std::string("D:/y")) == "D:/y", "appending an absolute volume name replaces the path"); + check(fs::path("D:/a/b").lexically_relative("D:/a") == "b" && fs::path("/a/b").is_absolute(), "a name relative to a volume, and `/' unchanged"); + std::error_code cwd_ec; + const fs::path cwd = fs::current_path(cwd_ec); + check(!cwd_ec && cwd.is_absolute(), "the working directory is absolute"); + check(fs::absolute(cwd, cwd_ec) == cwd && !cwd_ec, "absolute() of it is itself"); + const fs::path can = fs::canonical(cwd, cwd_ec); + check(!cwd_ec && can.is_absolute(), "canonical() of it succeeds"); + } + // --- std::filesystem, which is the C++ face of openkal.fs --------------- namespace fs = std::filesystem; diff --git a/examples/image-cxx/src/main.cpp b/examples/image-cxx/src/main.cpp index 0beae9cb..3250b646 100644 --- a/examples/image-cxx/src/main.cpp +++ b/examples/image-cxx/src/main.cpp @@ -39,6 +39,32 @@ int main(int argc, char** argv) { check(std::format("{:>6.2f}", 3.14159) == " 3.14", "formatting a floating-point value"); + // --- names beneath a volume (openkal-llvm-runtime#64) ------------------- + // + // The C library names a file on a system with volumes `D:/...` and takes + // `X:/` as a root. The same name is absolute to std::filesystem, and the + // working directory is absolute on every system. + { + namespace fs = std::filesystem; + const fs::path v = "D:/a/b.txt"; + check(v.is_absolute() && v.root_name() == "D:" && v.root_directory() == "/", "`D:/a/b.txt' has a root name and a root directory"); + check(v.relative_path() == "a/b.txt" && v.parent_path() == "D:/a" && v.filename() == "b.txt", "and decomposes beneath it"); + std::vector forward, backward; + for (const auto& e : v) forward.push_back(e.string()); + for (auto it = v.end(); it != v.begin();) backward.insert(backward.begin(), (--it)->string()); + check(forward == std::vector{"D:", "/", "a", "b.txt"} && backward == forward, "its elements are the same read either way"); + check(!fs::path("D:").is_absolute() && fs::path("D:").root_name().empty(), "`D:' without a separator is an ordinary name"); + check(!fs::path("D:x").is_absolute(), "`D:x' is relative"); + check((fs::path("x") / "D:/y") == "D:/y" && fs::path("x").append(std::string("D:/y")) == "D:/y", "appending an absolute volume name replaces the path"); + check(fs::path("D:/a/b").lexically_relative("D:/a") == "b" && fs::path("/a/b").is_absolute(), "a name relative to a volume, and `/' unchanged"); + std::error_code cwd_ec; + const fs::path cwd = fs::current_path(cwd_ec); + check(!cwd_ec && cwd.is_absolute(), "the working directory is absolute"); + check(fs::absolute(cwd, cwd_ec) == cwd && !cwd_ec, "absolute() of it is itself"); + const fs::path can = fs::canonical(cwd, cwd_ec); + check(!cwd_ec && can.is_absolute(), "canonical() of it succeeds"); + } + std::println("-- failures: {} --", failures); return failures ? 1 : 0; } diff --git a/llvm/PATCHES.md b/llvm/PATCHES.md index 08344946..37a3a31a 100644 --- a/llvm/PATCHES.md +++ b/llvm/PATCHES.md @@ -29,10 +29,10 @@ grep -rn "openkal ─── BEGIN" llvm/ ## 已替换 - 五处,四个文件,一个判据 —— 每一处都能填进上面那句话。 + 六处,五个文件,一个判据 —— 每一处都能填进上面那句话。 ```sh -grep -rn "openkal ─── BEGIN" llvm/ # 10 处标记(含配对的 #else/#endif) +grep -rn "openkal ─── BEGIN" llvm/ # 30 处 BEGIN 标记 ``` | 文件 | 上游按什么分派 | 平台面 | openkal 的答案 | @@ -42,6 +42,21 @@ grep -rn "openkal ─── BEGIN" llvm/ # 10 处标记(含配对的 #else/#e | `libunwind/src/RWMutex.hpp` | `_WIN32` | 读写锁 | pthread ⇒ musl ⇒ `kal_task_*` | | `libunwind/src/UnwindCursor.hpp` | `_WIN32` | (无) | 整段没被用到,只是被 include | | `compiler-rt/…/emutls.c` | `_WIN32` | 互斥 + 对齐分配 | pthread + `posix_memalign` | +| `libcxx/src/filesystem/path_parser.h` | `_LIBCPP_WIN32API` | 名字的根 | C 库的根:`/` 与 `X:/`(0.19.1) | + +### `libcxx/src/filesystem/path_parser.h` 与 `port/include/__filesystem/path.h` — 卷下的名字(0.19.1) + +C 库(openkal-musl)在有卷的系统上把文件写作 `D:/...`,并像对待 `/` 一样把 `X:/` 当作根 +(其 `root_length`)。libc++ 只在 `_LIBCPP_WIN32API` 下识别盘符,按 POSIX 规则读 `D:/a` 时它是 +相对名:工作目录的 `is_absolute()` 为假,`absolute`、`canonical`、`relative` 拼出不存在的名字 +(openkal-llvm-runtime#64)。 + +- `path_parser.h` 的 `consumeRootName` 在 `OPENKAL` 下接受后面紧跟 `/` 的 `X:` 作为根名,正向与 + 反向解析都一样;不带分隔符的 `X:` 仍是普通名字,与 C 库一致。`path_parser.h` 由同目录的 + `.cpp` 以引号包含,无法遮蔽,所以原地标记。 +- `__filesystem/path.h` 的 `append(Source)` 在 POSIX 分支只看首字符是否为分隔符;覆盖件在 + `OPENKAL` 或 `__OPENKAL__` 下改用 `path::is_absolute()` 判断,使 `path / "D:/y"` 与 + `path / path("D:/y")` 结果相同。头文件按本文的约定放在 `port/include/` 覆盖,vendored 树不动。 ### `libcxx/src/atomic.cpp` — `std::atomic` 的等待与唤醒 diff --git a/llvm/libcxx/src/filesystem/path_parser.h b/llvm/libcxx/src/filesystem/path_parser.h index 5968f449..c82584c4 100644 --- a/llvm/libcxx/src/filesystem/path_parser.h +++ b/llvm/libcxx/src/filesystem/path_parser.h @@ -329,6 +329,27 @@ struct PathParser { return Ret; if (PosPtr Ret = consumeNetworkRoot(P, End)) return Ret; +// ─── openkal ─── BEGIN ──────────────────────────────────────────────────────── +#elif defined(OPENKAL) + // openkal: A VOLUME IS A ROOT NAME WHEN A SEPARATOR FOLLOWS IT. + // + // The C library beneath (openkal-musl) names a file on a system with + // volumes `D:/...`, and takes `X:/` as a root exactly as it takes `/` + // (its root_length). Read as a POSIX name, `D:/a` was relative, so + // `current_path().is_absolute()` was false and `absolute`, `canonical` + // and `relative` built names that do not exist (openkal-llvm-runtime#64). + // `D:` without a separator stays an ordinary name, as it is to the C + // library. + if (P < End) { + if (End - P >= 3 && isDriveLetter(P[0]) && P[1] == ':' && P[2] == '/') + return P + 2; + } else if (P != End) { + const PosPtr First = Path.data(); + const PosPtr Last = Path.data() + Path.size(); + if (P - 1 == First && P - 1 > End && P[0] == ':' && isDriveLetter(P[-1]) && P + 1 < Last && P[1] == '/') + return P - 2; + } +// ─── openkal ─── END ────────────────────────────────────────────────────────── #endif return nullptr; } diff --git a/mcpp.toml b/mcpp.toml index e87bd803..7faa78e9 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-llvm-runtime" -version = "0.19.0" +version = "0.19.1" description = "LLVM's C++ runtime libraries — libc++, libc++abi and libunwind — configured for openkal-musl rather than for a host C library." license = "Apache-2.0" authors = ["mcpplibs"] @@ -230,10 +230,10 @@ cflags = ["-DDISABLE_AARCH64_FMV=1"] # (measured on OpenSBI: every request for an arena refused, ENOMEM). A # target's edge replaces a general one, so each states every feature. [target.'cfg(not(os = "none"))'.dependencies] -openkal-musl = { version = "0.28.0", default-features = false, features = ["posix", "malloc-mimalloc"] } +openkal-musl = { version = "^0.28.0", default-features = false, features = ["posix", "malloc-mimalloc"] } [target.'cfg(os = "none")'.dependencies] -openkal-musl = { version = "0.28.0", default-features = false, features = ["posix", "malloc-mallocng"] } +openkal-musl = { version = "^0.28.0", default-features = false, features = ["posix", "malloc-mallocng"] } [build] diff --git a/port/include/__filesystem/path.h b/port/include/__filesystem/path.h new file mode 100644 index 00000000..c14d318b --- /dev/null +++ b/port/include/__filesystem/path.h @@ -0,0 +1,947 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FILESYSTEM_PATH_H +#define _LIBCPP___FILESYSTEM_PATH_H + +#include <__algorithm/replace.h> +#include <__algorithm/replace_copy.h> +#include <__config> +#include <__functional/unary_function.h> +#include <__fwd/functional.h> +#include <__iterator/back_insert_iterator.h> +#include <__iterator/iterator_traits.h> +#include <__memory/addressof.h> +#include <__type_traits/decay.h> +#include <__type_traits/enable_if.h> +#include <__type_traits/is_pointer.h> +#include <__type_traits/remove_const.h> +#include <__type_traits/remove_pointer.h> +#include <__utility/move.h> +#include +#include + +#if _LIBCPP_HAS_LOCALIZATION +# include // for quoted +#endif + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +#if _LIBCPP_STD_VER >= 17 + +_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM + +template +struct __can_convert_char { + static const bool value = false; +}; +template +struct __can_convert_char : public __can_convert_char<_Tp> {}; +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type _LIBCPP_NODEBUG = char; +}; +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type _LIBCPP_NODEBUG = wchar_t; +}; +# if _LIBCPP_HAS_CHAR8_T +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type _LIBCPP_NODEBUG = char8_t; +}; +# endif +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type _LIBCPP_NODEBUG = char16_t; +}; +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type _LIBCPP_NODEBUG = char32_t; +}; + +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI bool __is_separator(_ECharT __e) { +# if defined(_LIBCPP_WIN32API) + return __e == _ECharT('/') || __e == _ECharT('\\'); +# else + return __e == _ECharT('/'); +# endif +} + +# if _LIBCPP_HAS_CHAR8_T +typedef u8string __u8_string; +# else +typedef string __u8_string; +# endif + +struct _NullSentinel {}; + +template +using _Void _LIBCPP_NODEBUG = void; + +template +struct __is_pathable_string : public false_type {}; + +template +struct __is_pathable_string< basic_string<_ECharT, _Traits, _Alloc>, + _Void::__char_type> > + : public __can_convert_char<_ECharT> { + using _Str _LIBCPP_NODEBUG = basic_string<_ECharT, _Traits, _Alloc>; + + _LIBCPP_HIDE_FROM_ABI static _ECharT const* __range_begin(_Str const& __s) { return __s.data(); } + + _LIBCPP_HIDE_FROM_ABI static _ECharT const* __range_end(_Str const& __s) { return __s.data() + __s.length(); } + + _LIBCPP_HIDE_FROM_ABI static _ECharT __first_or_null(_Str const& __s) { return __s.empty() ? _ECharT{} : __s[0]; } +}; + +template +struct __is_pathable_string< basic_string_view<_ECharT, _Traits>, + _Void::__char_type> > + : public __can_convert_char<_ECharT> { + using _Str _LIBCPP_NODEBUG = basic_string_view<_ECharT, _Traits>; + + _LIBCPP_HIDE_FROM_ABI static _ECharT const* __range_begin(_Str const& __s) { return __s.data(); } + + _LIBCPP_HIDE_FROM_ABI static _ECharT const* __range_end(_Str const& __s) { return __s.data() + __s.length(); } + + _LIBCPP_HIDE_FROM_ABI static _ECharT __first_or_null(_Str const& __s) { return __s.empty() ? _ECharT{} : __s[0]; } +}; + +template , + class _UnqualPtrType = __remove_const_t<__remove_pointer_t<_DS> >, + bool _IsCharPtr = is_pointer<_DS>::value && __can_convert_char<_UnqualPtrType>::value> +struct __is_pathable_char_array : false_type {}; + +template +struct __is_pathable_char_array<_Source, _ECharT*, _UPtr, true> : __can_convert_char<__remove_const_t<_ECharT> > { + _LIBCPP_HIDE_FROM_ABI static _ECharT const* __range_begin(const _ECharT* __b) { return __b; } + + _LIBCPP_HIDE_FROM_ABI static _ECharT const* __range_end(const _ECharT* __b) { + using _Iter = const _ECharT*; + const _ECharT __sentinel = _ECharT{}; + _Iter __e = __b; + for (; *__e != __sentinel; ++__e) + ; + return __e; + } + + _LIBCPP_HIDE_FROM_ABI static _ECharT __first_or_null(const _ECharT* __b) { return *__b; } +}; + +template ::value, class = void> +struct __is_pathable_iter : false_type {}; + +template +struct __is_pathable_iter< + _Iter, + true, + _Void::value_type>::__char_type> > + : __can_convert_char::value_type> { + using _ECharT _LIBCPP_NODEBUG = typename iterator_traits<_Iter>::value_type; + + _LIBCPP_HIDE_FROM_ABI static _Iter __range_begin(_Iter __b) { return __b; } + + _LIBCPP_HIDE_FROM_ABI static _NullSentinel __range_end(_Iter) { return _NullSentinel{}; } + + _LIBCPP_HIDE_FROM_ABI static _ECharT __first_or_null(_Iter __b) { return *__b; } +}; + +template ::value, + bool _IsCharIterT = __is_pathable_char_array<_Tp>::value, + bool _IsIterT = !_IsCharIterT && __is_pathable_iter<_Tp>::value> +struct __is_pathable : false_type { + static_assert(!_IsStringT && !_IsCharIterT && !_IsIterT, "Must all be false"); +}; + +template +struct __is_pathable<_Tp, true, false, false> : __is_pathable_string<_Tp> {}; + +template +struct __is_pathable<_Tp, false, true, false> : __is_pathable_char_array<_Tp> {}; + +template +struct __is_pathable<_Tp, false, false, true> : __is_pathable_iter<_Tp> {}; + +# if defined(_LIBCPP_WIN32API) +typedef wstring __path_string; +typedef wchar_t __path_value; +# else +typedef string __path_string; +typedef char __path_value; +# endif + +# if defined(_LIBCPP_WIN32API) +_LIBCPP_EXPORTED_FROM_ABI size_t __wide_to_char(const wstring&, char*, size_t); +_LIBCPP_EXPORTED_FROM_ABI size_t __char_to_wide(const string&, wchar_t*, size_t); +# endif + +template +struct _PathCVT; + +# if _LIBCPP_HAS_LOCALIZATION +template +struct _PathCVT { + static_assert(__can_convert_char<_ECharT>::value, "Char type not convertible"); + + typedef __narrow_to_utf8 _Narrower; +# if defined(_LIBCPP_WIN32API) + typedef __widen_from_utf8 _Widener; +# endif + + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _ECharT const* __b, _ECharT const* __e) { +# if defined(_LIBCPP_WIN32API) + string __utf8; + _Narrower()(back_inserter(__utf8), __b, __e); + _Widener()(back_inserter(__dest), __utf8.data(), __utf8.data() + __utf8.size()); +# else + _Narrower()(back_inserter(__dest), __b, __e); +# endif + } + + template + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _Iter __b, _Iter __e) { + static_assert(!is_same<_Iter, _ECharT*>::value, "Call const overload"); + if (__b == __e) + return; + basic_string<_ECharT> __tmp(__b, __e); +# if defined(_LIBCPP_WIN32API) + string __utf8; + _Narrower()(back_inserter(__utf8), __tmp.data(), __tmp.data() + __tmp.length()); + _Widener()(back_inserter(__dest), __utf8.data(), __utf8.data() + __utf8.size()); +# else + _Narrower()(back_inserter(__dest), __tmp.data(), __tmp.data() + __tmp.length()); +# endif + } + + template + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _Iter __b, _NullSentinel) { + static_assert(!is_same<_Iter, _ECharT*>::value, "Call const overload"); + const _ECharT __sentinel = _ECharT{}; + if (*__b == __sentinel) + return; + basic_string<_ECharT> __tmp; + for (; *__b != __sentinel; ++__b) + __tmp.push_back(*__b); +# if defined(_LIBCPP_WIN32API) + string __utf8; + _Narrower()(back_inserter(__utf8), __tmp.data(), __tmp.data() + __tmp.length()); + _Widener()(back_inserter(__dest), __utf8.data(), __utf8.data() + __utf8.size()); +# else + _Narrower()(back_inserter(__dest), __tmp.data(), __tmp.data() + __tmp.length()); +# endif + } + + template + _LIBCPP_HIDE_FROM_ABI static void __append_source(__path_string& __dest, _Source const& __s) { + using _Traits = __is_pathable<_Source>; + __append_range(__dest, _Traits::__range_begin(__s), _Traits::__range_end(__s)); + } +}; +# endif // _LIBCPP_HAS_LOCALIZATION + +template <> +struct _PathCVT<__path_value> { + template ::value, int> = 0> + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _Iter __b, _Iter __e) { + for (; __b != __e; ++__b) + __dest.push_back(*__b); + } + + template ::value, int> = 0> + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _Iter __b, _Iter __e) { + __dest.append(__b, __e); + } + + template + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _Iter __b, _NullSentinel) { + const char __sentinel = char{}; + for (; *__b != __sentinel; ++__b) + __dest.push_back(*__b); + } + + template + _LIBCPP_HIDE_FROM_ABI static void __append_source(__path_string& __dest, _Source const& __s) { + using _Traits = __is_pathable<_Source>; + __append_range(__dest, _Traits::__range_begin(__s), _Traits::__range_end(__s)); + } +}; + +# if defined(_LIBCPP_WIN32API) +template <> +struct _PathCVT { + _LIBCPP_HIDE_FROM_ABI static void __append_string(__path_string& __dest, const basic_string& __str) { + size_t __size = __char_to_wide(__str, nullptr, 0); + size_t __pos = __dest.size(); + __dest.resize(__pos + __size); + __char_to_wide(__str, const_cast<__path_value*>(__dest.data()) + __pos, __size); + } + + template ::value, int> = 0> + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _Iter __b, _Iter __e) { + basic_string __tmp(__b, __e); + __append_string(__dest, __tmp); + } + + template ::value, int> = 0> + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _Iter __b, _Iter __e) { + basic_string __tmp(__b, __e); + __append_string(__dest, __tmp); + } + + template + _LIBCPP_HIDE_FROM_ABI static void __append_range(__path_string& __dest, _Iter __b, _NullSentinel) { + const char __sentinel = char{}; + basic_string __tmp; + for (; *__b != __sentinel; ++__b) + __tmp.push_back(*__b); + __append_string(__dest, __tmp); + } + + template + _LIBCPP_HIDE_FROM_ABI static void __append_source(__path_string& __dest, _Source const& __s) { + using _Traits = __is_pathable<_Source>; + __append_range(__dest, _Traits::__range_begin(__s), _Traits::__range_end(__s)); + } +}; + +# if _LIBCPP_HAS_LOCALIZATION +template +struct _PathExport { + typedef __narrow_to_utf8 _Narrower; + typedef __widen_from_utf8 _Widener; + + template + _LIBCPP_HIDE_FROM_ABI static void __append(_Str& __dest, const __path_string& __src) { + string __utf8; + _Narrower()(back_inserter(__utf8), __src.data(), __src.data() + __src.size()); + _Widener()(back_inserter(__dest), __utf8.data(), __utf8.data() + __utf8.size()); + } +}; + +template <> +struct _PathExport { + template + _LIBCPP_HIDE_FROM_ABI static void __append(_Str& __dest, const __path_string& __src) { + size_t __size = __wide_to_char(__src, nullptr, 0); + size_t __pos = __dest.size(); + __dest.resize(__size); + __wide_to_char(__src, const_cast(__dest.data()) + __pos, __size); + } +}; + +template <> +struct _PathExport { + template + _LIBCPP_HIDE_FROM_ABI static void __append(_Str& __dest, const __path_string& __src) { + __dest.append(__src.begin(), __src.end()); + } +}; + +template <> +struct _PathExport { + template + _LIBCPP_HIDE_FROM_ABI static void __append(_Str& __dest, const __path_string& __src) { + __dest.append(__src.begin(), __src.end()); + } +}; + +# if _LIBCPP_HAS_CHAR8_T +template <> +struct _PathExport { + typedef __narrow_to_utf8 _Narrower; + + template + _LIBCPP_HIDE_FROM_ABI static void __append(_Str& __dest, const __path_string& __src) { + _Narrower()(back_inserter(__dest), __src.data(), __src.data() + __src.size()); + } +}; +# endif // _LIBCPP_HAS_CHAR8_T +# endif // _LIBCPP_HAS_LOCALIZATION +# endif // _LIBCPP_WIN32API + +class _LIBCPP_EXPORTED_FROM_ABI path { + template + using _EnableIfPathable _LIBCPP_NODEBUG = __enable_if_t<__is_pathable<_SourceOrIter>::value, _Tp>; + + template + using _SourceChar _LIBCPP_NODEBUG = typename __is_pathable<_Tp>::__char_type; + + template + using _SourceCVT _LIBCPP_NODEBUG = _PathCVT<_SourceChar<_Tp> >; + +public: +# if defined(_LIBCPP_WIN32API) + typedef wchar_t value_type; + static constexpr value_type preferred_separator = L'\\'; +# else + typedef char value_type; + static constexpr value_type preferred_separator = '/'; +# endif + typedef basic_string string_type; + typedef basic_string_view __string_view; + + enum format : unsigned char { auto_format, native_format, generic_format }; + + // constructors and destructor + _LIBCPP_HIDE_FROM_ABI path() noexcept {} + _LIBCPP_HIDE_FROM_ABI path(const path& __p) : __pn_(__p.__pn_) {} + _LIBCPP_HIDE_FROM_ABI path(path&& __p) noexcept : __pn_(std::move(__p.__pn_)) {} + + _LIBCPP_HIDE_FROM_ABI path(string_type&& __s, format = format::auto_format) noexcept : __pn_(std::move(__s)) {} + + template > + _LIBCPP_HIDE_FROM_ABI path(const _Source& __src, format = format::auto_format) { + _SourceCVT<_Source>::__append_source(__pn_, __src); + } + + template + _LIBCPP_HIDE_FROM_ABI path(_InputIt __first, _InputIt __last, format = format::auto_format) { + typedef typename iterator_traits<_InputIt>::value_type _ItVal; + _PathCVT<_ItVal>::__append_range(__pn_, __first, __last); + } + + /* + #if _LIBCPP_HAS_LOCALIZATION + // TODO Implement locale conversions. + template > + path(const _Source& __src, const locale& __loc, format = format::auto_format); + template + path(_InputIt __first, _InputIt _last, const locale& __loc, + format = format::auto_format); + #endif + */ + + _LIBCPP_HIDE_FROM_ABI ~path() = default; + + // assignments + _LIBCPP_HIDE_FROM_ABI path& operator=(const path& __p) { + __pn_ = __p.__pn_; + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& operator=(path&& __p) noexcept { + __pn_ = std::move(__p.__pn_); + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& operator=(string_type&& __s) noexcept { + __pn_ = std::move(__s); + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& assign(string_type&& __s) noexcept { + __pn_ = std::move(__s); + return *this; + } + + template + _LIBCPP_HIDE_FROM_ABI _EnableIfPathable<_Source> operator=(const _Source& __src) { + return this->assign(__src); + } + + template + _LIBCPP_HIDE_FROM_ABI _EnableIfPathable<_Source> assign(const _Source& __src) { + __pn_.clear(); + _SourceCVT<_Source>::__append_source(__pn_, __src); + return *this; + } + + template + _LIBCPP_HIDE_FROM_ABI path& assign(_InputIt __first, _InputIt __last) { + typedef typename iterator_traits<_InputIt>::value_type _ItVal; + __pn_.clear(); + _PathCVT<_ItVal>::__append_range(__pn_, __first, __last); + return *this; + } + +public: + // appends +# if defined(_LIBCPP_WIN32API) + _LIBCPP_HIDE_FROM_ABI path& operator/=(const path& __p) { + auto __p_root_name = __p.__root_name(); + auto __p_root_name_size = __p_root_name.size(); + if (__p.is_absolute() || (!__p_root_name.empty() && __p_root_name != __string_view(root_name().__pn_))) { + __pn_ = __p.__pn_; + return *this; + } + if (__p.has_root_directory()) { + path __root_name_str = root_name(); + __pn_ = __root_name_str.native(); + __pn_ += __string_view(__p.__pn_).substr(__p_root_name_size); + return *this; + } + if (has_filename() || (!has_root_directory() && is_absolute())) + __pn_ += preferred_separator; + __pn_ += __string_view(__p.__pn_).substr(__p_root_name_size); + return *this; + } + template + _LIBCPP_HIDE_FROM_ABI _EnableIfPathable<_Source> operator/=(const _Source& __src) { + return operator/=(path(__src)); + } + + template + _LIBCPP_HIDE_FROM_ABI _EnableIfPathable<_Source> append(const _Source& __src) { + return operator/=(path(__src)); + } + + template + _LIBCPP_HIDE_FROM_ABI path& append(_InputIt __first, _InputIt __last) { + return operator/=(path(__first, __last)); + } +# else + _LIBCPP_HIDE_FROM_ABI path& operator/=(const path& __p) { + if (__p.is_absolute()) { + __pn_ = __p.__pn_; + return *this; + } + if (has_filename()) + __pn_ += preferred_separator; + __pn_ += __p.native(); + return *this; + } + + // FIXME: Use _LIBCPP_DIAGNOSE_WARNING to produce a diagnostic when __src + // is known at compile time to be "/' since the user almost certainly intended + // to append a separator instead of overwriting the path with "/" + template + _LIBCPP_HIDE_FROM_ABI _EnableIfPathable<_Source> operator/=(const _Source& __src) { + return this->append(__src); + } + + template + _LIBCPP_HIDE_FROM_ABI _EnableIfPathable<_Source> append(const _Source& __src) { + using _Traits = __is_pathable<_Source>; + using _CVT = _PathCVT<_SourceChar<_Source> >; + bool __source_is_absolute = filesystem::__is_separator(_Traits::__first_or_null(__src)); +# if defined(OPENKAL) || defined(__OPENKAL__) + // openkal: `X:/...` is absolute as well (openkal-llvm-runtime#64); the + // path overload decides it, as it does for a path argument. + if (!__source_is_absolute) { + path __whole(__src); + if (__whole.is_absolute()) { + __pn_ = std::move(__whole.__pn_); + return *this; + } + } +# endif + if (__source_is_absolute) + __pn_.clear(); + else if (has_filename()) + __pn_ += preferred_separator; + _CVT::__append_source(__pn_, __src); + return *this; + } + + template + _LIBCPP_HIDE_FROM_ABI path& append(_InputIt __first, _InputIt __last) { + typedef typename iterator_traits<_InputIt>::value_type _ItVal; + static_assert(__can_convert_char<_ItVal>::value, "Must convertible"); + using _CVT = _PathCVT<_ItVal>; + if (__first != __last && filesystem::__is_separator(*__first)) + __pn_.clear(); + else if (has_filename()) + __pn_ += preferred_separator; + _CVT::__append_range(__pn_, __first, __last); + return *this; + } +# endif + + // concatenation + _LIBCPP_HIDE_FROM_ABI path& operator+=(const path& __x) { + __pn_ += __x.__pn_; + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& operator+=(const string_type& __x) { + __pn_ += __x; + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& operator+=(__string_view __x) { + __pn_ += __x; + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& operator+=(const value_type* __x) { + __pn_ += __x; + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& operator+=(value_type __x) { + __pn_ += __x; + return *this; + } + + template ::value, int> = 0> + _LIBCPP_HIDE_FROM_ABI path& operator+=(_ECharT __x) { + _PathCVT<_ECharT>::__append_source(__pn_, basic_string_view<_ECharT>(std::addressof(__x), 1)); + return *this; + } + + template + _LIBCPP_HIDE_FROM_ABI _EnableIfPathable<_Source> operator+=(const _Source& __x) { + return this->concat(__x); + } + + template + _LIBCPP_HIDE_FROM_ABI _EnableIfPathable<_Source> concat(const _Source& __x) { + _SourceCVT<_Source>::__append_source(__pn_, __x); + return *this; + } + + template + _LIBCPP_HIDE_FROM_ABI path& concat(_InputIt __first, _InputIt __last) { + typedef typename iterator_traits<_InputIt>::value_type _ItVal; + _PathCVT<_ItVal>::__append_range(__pn_, __first, __last); + return *this; + } + + // modifiers + _LIBCPP_HIDE_FROM_ABI void clear() noexcept { __pn_.clear(); } + + _LIBCPP_HIDE_FROM_ABI path& make_preferred() { +# if defined(_LIBCPP_WIN32API) + std::replace(__pn_.begin(), __pn_.end(), L'/', L'\\'); +# endif + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& remove_filename() { + auto __fname = __filename(); + if (!__fname.empty()) + __pn_.erase(__fname.data() - __pn_.data()); + return *this; + } + + _LIBCPP_HIDE_FROM_ABI path& replace_filename(const path& __replacement) { + remove_filename(); + return (*this /= __replacement); + } + + path& replace_extension(const path& __replacement = path()); + + friend _LIBCPP_HIDE_FROM_ABI bool operator==(const path& __lhs, const path& __rhs) noexcept { + return __lhs.__compare(__rhs.__pn_) == 0; + } +# if _LIBCPP_STD_VER <= 17 + friend _LIBCPP_HIDE_FROM_ABI bool operator!=(const path& __lhs, const path& __rhs) noexcept { + return __lhs.__compare(__rhs.__pn_) != 0; + } + friend _LIBCPP_HIDE_FROM_ABI bool operator<(const path& __lhs, const path& __rhs) noexcept { + return __lhs.__compare(__rhs.__pn_) < 0; + } + friend _LIBCPP_HIDE_FROM_ABI bool operator<=(const path& __lhs, const path& __rhs) noexcept { + return __lhs.__compare(__rhs.__pn_) <= 0; + } + friend _LIBCPP_HIDE_FROM_ABI bool operator>(const path& __lhs, const path& __rhs) noexcept { + return __lhs.__compare(__rhs.__pn_) > 0; + } + friend _LIBCPP_HIDE_FROM_ABI bool operator>=(const path& __lhs, const path& __rhs) noexcept { + return __lhs.__compare(__rhs.__pn_) >= 0; + } +# else // _LIBCPP_STD_VER <= 17 + friend _LIBCPP_HIDE_FROM_ABI strong_ordering operator<=>(const path& __lhs, const path& __rhs) noexcept { + return __lhs.__compare(__rhs.__pn_) <=> 0; + } +# endif // _LIBCPP_STD_VER <= 17 + + friend _LIBCPP_HIDE_FROM_ABI path operator/(const path& __lhs, const path& __rhs) { + path __result(__lhs); + __result /= __rhs; + return __result; + } + + _LIBCPP_HIDE_FROM_ABI void swap(path& __rhs) noexcept { __pn_.swap(__rhs.__pn_); } + + // private helper to allow reserving memory in the path + _LIBCPP_HIDE_FROM_ABI void __reserve(size_t __s) { __pn_.reserve(__s); } + + // native format observers + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const string_type& native() const noexcept { return __pn_; } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI const value_type* c_str() const noexcept { return __pn_.c_str(); } + + _LIBCPP_HIDE_FROM_ABI operator string_type() const { return __pn_; } + +# if defined(_LIBCPP_WIN32API) + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::wstring wstring() const { return __pn_; } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::wstring generic_wstring() const { + std::wstring __s; + __s.resize(__pn_.size()); + std::replace_copy(__pn_.begin(), __pn_.end(), __s.begin(), '\\', '/'); + return __s; + } + +# if _LIBCPP_HAS_LOCALIZATION + template , class _Allocator = allocator<_ECharT> > + [[nodiscard]] + _LIBCPP_HIDE_FROM_ABI basic_string<_ECharT, _Traits, _Allocator> string(const _Allocator& __a = _Allocator()) const { + using _Str = basic_string<_ECharT, _Traits, _Allocator>; + _Str __s(__a); + __s.reserve(__pn_.size()); + _PathExport<_ECharT>::__append(__s, __pn_); + return __s; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::string string() const { return string(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI __u8_string u8string() const { + using _CVT = __narrow_to_utf8; + __u8_string __s; + __s.reserve(__pn_.size()); + _CVT()(back_inserter(__s), __pn_.data(), __pn_.data() + __pn_.size()); + return __s; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u16string u16string() const { return string(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u32string u32string() const { return string(); } + + // generic format observers + template , class _Allocator = allocator<_ECharT> > + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI basic_string<_ECharT, _Traits, _Allocator> + generic_string(const _Allocator& __a = _Allocator()) const { + using _Str = basic_string<_ECharT, _Traits, _Allocator>; + _Str __s = string<_ECharT, _Traits, _Allocator>(__a); + // Note: This (and generic_u8string below) is slightly suboptimal as + // it iterates twice over the string; once to convert it to the right + // character type, and once to replace path delimiters. + std::replace(__s.begin(), __s.end(), static_cast<_ECharT>('\\'), static_cast<_ECharT>('/')); + return __s; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::string generic_string() const { return generic_string(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u16string generic_u16string() const { return generic_string(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u32string generic_u32string() const { return generic_string(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI __u8_string generic_u8string() const { + __u8_string __s = u8string(); + std::replace(__s.begin(), __s.end(), '\\', '/'); + return __s; + } +# endif // _LIBCPP_HAS_LOCALIZATION +# else /* _LIBCPP_WIN32API */ + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::string string() const { return __pn_; } +# if _LIBCPP_HAS_CHAR8_T + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u8string u8string() const { + return std::u8string(__pn_.begin(), __pn_.end()); + } +# else + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::string u8string() const { return __pn_; } +# endif + +# if _LIBCPP_HAS_LOCALIZATION + template , class _Allocator = allocator<_ECharT> > + [[nodiscard]] + _LIBCPP_HIDE_FROM_ABI basic_string<_ECharT, _Traits, _Allocator> string(const _Allocator& __a = _Allocator()) const { + using _CVT = __widen_from_utf8; + using _Str = basic_string<_ECharT, _Traits, _Allocator>; + _Str __s(__a); + __s.reserve(__pn_.size()); + _CVT()(std::back_inserter(__s), __pn_.data(), __pn_.data() + __pn_.size()); + return __s; + } + +# if _LIBCPP_HAS_WIDE_CHARACTERS + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::wstring wstring() const { return string(); } +# endif + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u16string u16string() const { return string(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u32string u32string() const { return string(); } +# endif // _LIBCPP_HAS_LOCALIZATION + + // generic format observers + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::string generic_string() const { return __pn_; } +# if _LIBCPP_HAS_CHAR8_T + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u8string generic_u8string() const { + return std::u8string(__pn_.begin(), __pn_.end()); + } +# else + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::string generic_u8string() const { return __pn_; } +# endif + +# if _LIBCPP_HAS_LOCALIZATION + template , class _Allocator = allocator<_ECharT> > + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI basic_string<_ECharT, _Traits, _Allocator> + generic_string(const _Allocator& __a = _Allocator()) const { + return string<_ECharT, _Traits, _Allocator>(__a); + } + +# if _LIBCPP_HAS_WIDE_CHARACTERS + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::wstring generic_wstring() const { return string(); } +# endif + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u16string generic_u16string() const { return string(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI std::u32string generic_u32string() const { return string(); } +# endif // _LIBCPP_HAS_LOCALIZATION +# endif /* !_LIBCPP_WIN32API */ + +private: + int __compare(__string_view) const; + __string_view __root_name() const; + __string_view __root_directory() const; + __string_view __root_path_raw() const; + __string_view __relative_path() const; + __string_view __parent_path() const; + __string_view __filename() const; + __string_view __stem() const; + __string_view __extension() const; + +public: + // compare + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI int compare(const path& __p) const noexcept { return __compare(__p.__pn_); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI int compare(const string_type& __s) const { return __compare(__s); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI int compare(__string_view __s) const { return __compare(__s); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI int compare(const value_type* __s) const { return __compare(__s); } + + // decomposition + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path root_name() const { return string_type(__root_name()); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path root_directory() const { return string_type(__root_directory()); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path root_path() const { +# if defined(_LIBCPP_WIN32API) + return string_type(__root_path_raw()); +# else + return root_name().append(string_type(__root_directory())); +# endif + } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path relative_path() const { return string_type(__relative_path()); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path parent_path() const { return string_type(__parent_path()); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path filename() const { return string_type(__filename()); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path stem() const { return string_type(__stem()); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path extension() const { return string_type(__extension()); } + + // query + [[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI bool empty() const noexcept { return __pn_.empty(); } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool has_root_name() const { return !__root_name().empty(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool has_root_directory() const { return !__root_directory().empty(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool has_root_path() const { return !__root_path_raw().empty(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool has_relative_path() const { return !__relative_path().empty(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool has_parent_path() const { return !__parent_path().empty(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool has_filename() const { return !__filename().empty(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool has_stem() const { return !__stem().empty(); } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool has_extension() const { return !__extension().empty(); } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool is_absolute() const { +# if defined(_LIBCPP_WIN32API) + __string_view __root_name_str = __root_name(); + __string_view __root_dir = __root_directory(); + if (__root_name_str.size() == 2 && __root_name_str[1] == ':') { + // A drive letter with no root directory is relative, e.g. x:example. + return !__root_dir.empty(); + } + // If no root name, it's relative, e.g. \example is relative to the current drive + if (__root_name_str.empty()) + return false; + if (__root_name_str.size() < 3) + return false; + // A server root name, like \\server, is always absolute + if (__root_name_str[0] != '/' && __root_name_str[0] != '\\') + return false; + if (__root_name_str[1] != '/' && __root_name_str[1] != '\\') + return false; + // Seems to be a server root name + return true; +# else + return has_root_directory(); +# endif + } + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool is_relative() const { return !is_absolute(); } + + // relative paths + [[nodiscard]] path lexically_normal() const; + [[nodiscard]] path lexically_relative(const path& __base) const; + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI path lexically_proximate(const path& __base) const { + path __result = this->lexically_relative(__base); + if (__result.native().empty()) + return *this; + return __result; + } + + // iterators + class iterator; + typedef iterator const_iterator; + + [[nodiscard]] iterator begin() const; + [[nodiscard]] iterator end() const; + +# if _LIBCPP_HAS_LOCALIZATION + template < + class _CharT, + class _Traits, + __enable_if_t::value && is_same<_Traits, char_traits >::value, int> = 0> + _LIBCPP_HIDE_FROM_ABI friend basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const path& __p) { + __os << std::__quoted(__p.native()); + return __os; + } + + template < + class _CharT, + class _Traits, + __enable_if_t::value || !is_same<_Traits, char_traits >::value, int> = 0> + _LIBCPP_HIDE_FROM_ABI friend basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const path& __p) { + __os << std::__quoted(__p.string<_CharT, _Traits>()); + return __os; + } + + template + _LIBCPP_HIDE_FROM_ABI friend basic_istream<_CharT, _Traits>& + operator>>(basic_istream<_CharT, _Traits>& __is, path& __p) { + basic_string<_CharT, _Traits> __tmp; + __is >> std::__quoted(__tmp); + __p = __tmp; + return __is; + } +# endif // _LIBCPP_HAS_LOCALIZATION + +private: + inline _LIBCPP_HIDE_FROM_ABI path& __assign_view(__string_view const& __s) { + __pn_ = string_type(__s); + return *this; + } + string_type __pn_; +}; + +inline _LIBCPP_HIDE_FROM_ABI void swap(path& __lhs, path& __rhs) noexcept { __lhs.swap(__rhs); } + +[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI size_t hash_value(const path& __p) noexcept; + +_LIBCPP_END_NAMESPACE_FILESYSTEM + +_LIBCPP_BEGIN_NAMESPACE_STD + +template <> +struct hash : __unary_function { + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t operator()(filesystem::path const& __p) const noexcept { + return filesystem::hash_value(__p); + } +}; + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STD_VER >= 17 + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FILESYSTEM_PATH_H