- Filesystem
Filesystem
This is a header-only single-file std::filesystem
compatible helper library,
based on the C++17 and C++20 specs, but implemented for C++11, C++14, C++17 or C++20
(tightly following the C++17 standard with very few documented exceptions). It is currently tested on
macOS 10.12/10.14/10.15/11.6, Windows 10, Ubuntu 18.04, Ubuntu 20.04, CentOS 7, CentOS 8, FreeBSD 12,
Alpine ARM/ARM64 Linux and Solaris 10 but should work on other systems too, as long as you have
at least a C++11 compatible compiler. It should work with Android NDK, Emscripten and I even
had reports of it being used on iOS (within sandboxing constraints) and with v1.5.6 there
is experimental support for QNX. The support of Android NDK, Emscripten, QNX, and since 1.5.14
GNU/Hurd and Haiku is not backed up by automated testing but PRs and bug reports are welcome
for those too and they are reported to work.
It is of course in its own namespace ghc::filesystem
to not interfere with a regular std::filesystem
should you use it in a mixed C++17 environment (which is possible).
Test coverage is well above 90%, and starting with v1.3.6 and in v1.5.0 more time was invested in benchmarking and optimizing parts of the library. I'll try to continue to optimize some parts and refactor others, striving to improve it as long as it doesn't introduce additional C++17/C++20 compatibility issues. Feedback is always welcome. Simply open an issue if you see something missing or wrong or not behaving as expected and I'll comment.
Motivation
I'm often in need of filesystem functionality, mostly fs::path
, but directory
access too, and when beginning to use C++11, I used that language update
to try to reduce my third-party dependencies. I could drop most of what
I used, but still missed some stuff that I started implementing for the
fun of it. Originally I based these helpers on my own coding- and naming
conventions. When C++17 was finalized, I wanted to use that interface,
but it took a while, to push myself to convert my classes.
The implementation is closely based on chapter 30.10 from the C++17 standard
and a draft close to that version is
Working Draft N4687.
It is from after the standardization of C++17 but it contains the latest filesystem
interface changes compared to the
Working Draft N4659.
Staring with v1.4.0, when compiled using C++20, it adapts to the changes according to path sorting order
and std::u8string
handling from Working Draft N4860.
I want to thank the people working on improving C++, I really liked how the language evolved with C++11 and the following standards. Keep on the good work!
Why the namespace GHC?
If you ask yourself, what ghc
is standing for, it is simply
gulraks helper classes
, yeah, I know, not very imaginative, but I wanted a
short namespace and I use it in some of my private classes (so it has nothing
to do with Haskell, sorry for the name clash).
Platforms
ghc::filesystem
is developed on macOS but CI tested on macOS, Windows,
various Linux Distributions, FreeBSD and starting with v1.5.12 on Solaris.
It should work on any of these with a C++11-capable compiler. Also there are some
checks to hopefully better work on Android, but as I currently don't test with the
Android NDK, I wouldn't call it a supported platform yet, same is valid for using
it with Emscripten. It is now part of the detected platforms, I fixed the obvious
issues and ran some tests with it, so it should be fine. All in all, I don't see it
replacing std::filesystem
where full C++17 or C++20 is available, it doesn't try
to be a "better" std::filesystem
, just an almost drop-in if you can't use it
(with the exception of the UTF-8 preference).
:information_source: Important: This implementation is following the "UTF-8 Everywhere" philosophy in that all
std::string
instances will be interpreted the same as std::u8string
encoding
wise and as being in UTF-8. The std::u16string
will be seen as UTF-16. See Differences in API
for more information.
Unit tests are currently run with:
- macOS 10.12: Xcode 9.2 (clang-900.0.39.2), GCC 9.2, Clang 9.0, macOS 10.13: Xcode 10.1, macOS 10.14: Xcode 11.2, macOS 10.15: Xcode 11.6, Xcode 12.4
- Windows: Visual Studio 2017, Visual Studio 2015, Visual Studio 2019, MinGW GCC 6.3 (Win32), GCC 7.2 (Win64), Cygwin GCC 10.2 (no CI yet)
- Linux (Ubuntu): GCC (5.5, 6.5, 7.4, 8.3, 9.2), Clang (5.0, 6.0, 7.1, 8.0, 9.0)
Linux (Alpine ARM/ARM64): GCC 9.2.0(The Drone build scripts stopped working, as they where a contribution, I couldn't figure out what went wrong, any help appreciated.)- FreeBSD: Clang 8.0
- Solaris: GCC 5.5
Tests
The header comes with a set of unit-tests and uses CMake as a build tool and Catch2 as test framework. All tests are registered with in CMake, so the ctest commando can be used to run the tests.
All tests against this implementation should succeed, depending on your environment it might be that there are some warnings, e.g. if you have no rights to create Symlinks on Windows or at least the test thinks so, but these are just informative.
To build the tests from inside the project directory under macOS or Linux just:
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
ctest
This generates the test binaries that run the tests and the last command executes them.
If the default compiler is a GCC 8 or newer, or Clang 7 or newer, it
additionally tries to build a version of the test binary compiled against GCCs/Clangs
std::filesystem
implementation, named std_filesystem_test
as an additional test of conformance. Ideally all tests should compile and
succeed with all filesystem implementations, but in reality, there are
some differences in behavior, sometimes due to room for interpretation in
in the standard, and there might be issues in these implementations too.
Usage
Downloads
The latest release version is v1.5.14 and source archives can be found here.
The latest pre-native-backend version is v1.4.0 and source archives can be found here.
The latest pre-C++20-support release version is v1.3.10 and source archives can be found here.
Currently only the latest minor release version receives bugfixes, so if possible, you should use the latest release.
Using it as Single-File-Header
As ghc::filesystem
is at first a header-only library, it should be enough to copy the header
or the include/ghc
directory into your project folder or point your include path to this place and
simply include the filesystem.hpp
header (or ghc/filesystem.hpp
if you use the subdirectory).
Everything is in the namespace ghc::filesystem
, so one way to use it only as
a fallback could be:
#if _MSVC_LANG >= 201703L || __cplusplus >= 201703L && defined(__has_include)
// ^ Supports MSVC prior to 15.7 without setting /Zc:__cplusplus to fix __cplusplus
// _MSVC_LANG works regardless. But without the switch, the compiler always reported 199711L: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/
#if __has_include(<filesystem>) // Two stage __has_include needed for MSVC 2015 and per https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005finclude.html
#define GHC_USE_STD_FS
// Old Apple OSs don't support std::filesystem, though the header is available at compile
// time. In particular, std::filesystem is unavailable before macOS 10.15, iOS/tvOS 13.0,
// and watchOS 6.0.
#ifdef __APPLE__
#include <Availability.h>
// Note: This intentionally uses std::filesystem on any new Apple OS, like visionOS
// released after std::filesystem, where std::filesystem is always available.
// (All other __<platform>_VERSION_MIN_REQUIREDs will be undefined and thus 0.)
#if __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 \
|| __IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 \
|| __TV_OS_VERSION_MIN_REQUIRED && __TV_OS_VERSION_MIN_REQUIRED < 130000 \
|| __WATCH_OS_VERSION_MAX_ALLOWED && __WATCH_OS_VERSION_MAX_ALLOWED < 60000
#undef GHC_USE_STD_FS
#endif
#endif
#endif
#endif
#ifdef GHC_USE_STD_FS
#include <filesystem>
namespace fs = std::filesystem;
#else
#include "filesystem.hpp"
namespace fs = ghc::filesystem;
#endif
If you want to also use the fstream
wrapper with path
support as fallback,
you might use:
#if _MSVC_LANG >= 201703L || __cplusplus >= 201703L && defined(__has_include)
// ^ Supports MSVC prior to 15.7 without setting /Zc:__cplusplus to fix __cplusplus
// _MSVC_LANG works regardless. But without the switch, the compiler always reported 199711L: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/
#if __has_include(<filesystem>) // Two stage __has_include needed for MSVC 2015 and per https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005finclude.html
#define GHC_USE_STD_FS
// Old Apple OSs don't support std::filesystem, though the header is available at compile
// time. In particular, std::filesystem is unavailable before macOS 10.15, iOS/tvOS 13.0,
// and watchOS 6.0.
#ifdef __APPLE__
#include <Availability.h>
// Note: This intentionally uses std::filesystem on any new Apple OS, like visionOS
// released after std::filesystem, where std::filesystem is always available.
// (All other __<platform>_VERSION_MIN_REQUIREDs will be undefined and thus 0.)
#if __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 \
|| __IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 \
|| __TV_OS_VERSION_MIN_REQUIRED && __TV_OS_VERSION_MIN_REQUIRED < 130000 \
|| __WATCH_OS_VERSION_MAX_ALLOWED && __WATCH_OS_VERSION_MAX_ALLOWED < 60000
#undef GHC_USE_STD_FS
#endif
#endif
#endif
#endif
#ifdef GHC_USE_STD_FS
#include <filesystem>
namespace fs {
using namespace std::filesystem;
using ifstream = std::ifstream;
using ofstream = std::ofstream;
using fstream = std::fstream;
}
#else
#include "filesystem.hpp"
namespace fs {
using namespace ghc::filesystem;
using ifstream = ghc::filesystem::ifstream;
using ofstream = ghc::filesystem::ofstream;
using fstream = ghc::filesystem::fstream;
}
#endif
Now you have e.g. fs::ofstream out(somePath);
and it is either the wrapper or
the C++17 std::ofstream
.
:information_source: Be aware, as a header-only library, it is not hiding the fact, that it
uses system includes, so they "pollute" your global namespace. Use the
forwarding-/implementation-header based approach (see below) to avoid this.
For Windows it needs Windows.h
and it might be a good idea to define
WIN32_LEAN_AND_MEAN
or NOMINMAX
prior to including filesystem.hpp
or
fs_std.hpp
headers to reduce pollution of your global namespace and compile
time. They are not defined by ghc::filesystem
to allow combination with contexts
where the full Windows.h
is needed, e.g. for UI elements.
:information_source: Hint: There is an additional header named ghc/fs_std.hpp
that implements this
dynamic selection of a filesystem implementation, that you can include
instead of ghc/filesystem.hpp
when you want std::filesystem
where
available and ghc::filesystem
where not.
Using it as Forwarding-/Implementation-Header
Alternatively, starting from v1.1.0 ghc::filesystem
can also be used by
including one of two additional wrapper headers. These allow to include
a forwarded version in most places (ghc/fs_fwd.hpp
) while hiding the
implementation details in a single cpp file that