commit 674b3f7cd8f421cf2edbc675c9464d790823257e Author: Ryan Date: Tue Dec 24 22:18:00 2024 +0100 Hello libcode-seafire-common diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ec9f3de --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +indent_size = 4 +max_line_length = off +trim_trailing_whitespace = false + +[*.yaml] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitea/workflows/on-push.yaml b/.gitea/workflows/on-push.yaml new file mode 100644 index 0000000..23a9e11 --- /dev/null +++ b/.gitea/workflows/on-push.yaml @@ -0,0 +1,24 @@ +name: on-push +on: [push] + +jobs: + build-and-test: + runs-on: linux + container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest + volumes: + - /build + steps: + - name: Clone repository + uses: actions/checkout@v3 + - name: Authenticate + run: | + git config unset http.https://code.helloryan.se/.extraheader + echo "${{ secrets.NETRC }}" >> ~/.netrc + - name: Initialize + run: | + bpkg create -d /build cc config.cc.coptions="-Wall -Werror" + bdep init -A /build + - name: Build + run: b + - name: Test + run: b test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c19e5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +.bdep/ +Doxyfile + +# Local default options files. +# +.build2/local/ + +# Compiler/linker output. +# +*.d +*.t +*.i +*.i.* +*.ii +*.ii.* +*.o +*.obj +*.gcm +*.pcm +*.ifc +*.so +*.dylib +*.dll +*.a +*.lib +*.exp +*.pdb +*.ilk +*.exe +*.exe.dlls/ +*.exe.manifest +*.pc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dfc745b --- /dev/null +++ b/LICENSE @@ -0,0 +1,31 @@ +Copyright © 2024 Ryan. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. All advertising materials mentioning features or use of this software must + display the following acknowledgement: + + This product includes software developed by Ryan, http://helloryan.se/. + +4. Neither the name(s) of the copyright holder(s) nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..683bca3 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# libcode-seafire-common + +![Build status](https://code.helloryan.se/code/libcode-seafire-common/actions/workflows/on-push.yaml/badge.svg) + +## Requirements + +None, other than a modern C++-compiler. + +## Building + +See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for +build instructions. + +## Contact + +Please report bugs and issues by sending an e-mail to: ryan@helloryan.se. + +## Contributing + +Please send an e-mail to ryan@helloryan.se to request an account and +write-access to the libcode-seafire-common repository. diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..974e01d --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,4 @@ +/config.build +/root/ +/bootstrap/ +build/ diff --git a/build/bootstrap.build b/build/bootstrap.build new file mode 100644 index 0000000..00c424a --- /dev/null +++ b/build/bootstrap.build @@ -0,0 +1,7 @@ +project = libcode-seafire-common + +using version +using config +using test +using install +using dist diff --git a/build/export.build b/build/export.build new file mode 100644 index 0000000..ec7d475 --- /dev/null +++ b/build/export.build @@ -0,0 +1,6 @@ +$out_root/ +{ + include code/seafire/common/ +} + +export $out_root/code/seafire/common/$import.target diff --git a/build/root.build b/build/root.build new file mode 100644 index 0000000..21e0a2e --- /dev/null +++ b/build/root.build @@ -0,0 +1,16 @@ +# Uncomment to suppress warnings coming from external libraries. +# +#cxx.internal.scope = current + +cxx.std = latest + +using cxx + +hxx{*}: extension = hxx +ixx{*}: extension = ixx +txx{*}: extension = txx +cxx{*}: extension = cxx + +# The test target for cross-testing (running tests under Wine, etc). +# +test.target = $cxx.target diff --git a/buildfile b/buildfile new file mode 100644 index 0000000..bbe185e --- /dev/null +++ b/buildfile @@ -0,0 +1,5 @@ +./: {code/ tests/} doc{README.md} legal{LICENSE} manifest + +# Don't install tests. +# +tests/: install = false diff --git a/code/seafire/common/.gitignore b/code/seafire/common/.gitignore new file mode 100644 index 0000000..b1ed0e0 --- /dev/null +++ b/code/seafire/common/.gitignore @@ -0,0 +1,9 @@ +# Generated version header. +# +version.hxx + +# Unit test executables and Testscript output directories +# (can be symlinks). +# +*.test +test-*.test diff --git a/code/seafire/common/allocator.cxx b/code/seafire/common/allocator.cxx new file mode 100644 index 0000000..0a46f58 --- /dev/null +++ b/code/seafire/common/allocator.cxx @@ -0,0 +1,15 @@ +#include + +namespace code::seafire::common +{ + + allocator_t:: + allocator_t() = default; + + allocator_t::allocation_t:: + allocation_t() = default; + + allocator_t::allocation_t:: + ~allocation_t() noexcept = default; + +} // namespace code::seafire::common diff --git a/code/seafire/common/allocator.hxx b/code/seafire/common/allocator.hxx new file mode 100644 index 0000000..2e0c8a8 --- /dev/null +++ b/code/seafire/common/allocator.hxx @@ -0,0 +1,66 @@ +#ifndef code__seafire__common__allocator_hxx_ +#define code__seafire__common__allocator_hxx_ + +#include +#include + +namespace code::seafire::common +{ + + /// Implements a memory allocator keeping track of allocations. + /// + /// All allocations made through an allocator_t are valid for the + /// lifetime of the allocator_t object. + /// + class allocator_t + { + public: + allocator_t(); + + allocator_t(allocator_t const&) = delete; + allocator_t(allocator_t&&) = delete; + + template + T& + alloc(); + + template + T& + alloc(T const&); + + template + T& + alloc(T&&); + + template + T& + alloc_emplace(Args&&...); + + allocator_t& operator=(allocator_t const&) = delete; + allocator_t& operator=(allocator_t&&) = delete; + + private: + struct allocation_t + { + allocation_t(); + + allocation_t(allocation_t const&) = delete; + allocation_t(allocation_t&&) = delete; + + virtual + ~allocation_t() noexcept; + + allocation_t& operator=(allocation_t const&) = delete; + allocation_t& operator=(allocation_t&&) = delete; + + }; + + std::set> allocations_; + + }; + +} // namespace code::seafire::common + +#include + +#endif diff --git a/code/seafire/common/allocator.txx b/code/seafire/common/allocator.txx new file mode 100644 index 0000000..75d3103 --- /dev/null +++ b/code/seafire/common/allocator.txx @@ -0,0 +1,55 @@ +namespace code::seafire::common +{ + + template + T& + allocator_t:: + alloc() + { + return alloc_emplace(); + } + + template + T& + allocator_t:: + alloc(T const& object) + { + return alloc_emplace(object); + } + + template + T& + allocator_t:: + alloc(T&& object) + { + return alloc_emplace(object); + } + + template + T& + allocator_t:: + alloc_emplace(Args&&... args) + { + struct object_t + : allocation_t + { + object_t(Args&&... args) + : object{std::forward(args)...} + {} + + T object; + + }; + + std::unique_ptr a{ + new object_t{std::forward(args)...} + }; + + auto& ref = a->object; + + allocations_.emplace(std::move(a)); + + return ref; + } + +} // namespace code::seafire::common diff --git a/code/seafire/common/buildfile b/code/seafire/common/buildfile new file mode 100644 index 0000000..5069dc8 --- /dev/null +++ b/code/seafire/common/buildfile @@ -0,0 +1,65 @@ +intf_libs = # Interface dependencies. +impl_libs = # Implementation dependencies. + +import intf_libs =+ libasio%lib{asio} + +./: lib{code-seafire-common}: libul{code-seafire-common} + +libul{code-seafire-common}: {hxx ixx txx cxx}{** -**.test... -version} \ + {hxx }{ version} + +libul{code-seafire-common}: $impl_libs $intf_libs + +# Unit tests. +# +exe{*.test}: +{ + test = true + install = false +} + +for t: cxx{**.test...} +{ + d = $directory($t) + n = $name($t)... + + ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} + $d/exe{$n}: libul{code-seafire-common}: bin.whole = false +} + +hxx{version}: in{version} $src_root/manifest +{ + dist = true + clean = ($src_root != $out_root) +} + +# Build options. +# +cxx.poptions =+ "-I$out_root" "-I$src_root" +cxx.coptions =+ -fvisibility=hidden + +# Export options. +# +lib{code-seafire-common}: +{ + cxx.export.poptions = "-I$out_root" "-I$src_root" + cxx.export.libs = $intf_libs +} + +# For pre-releases use the complete version to make sure they cannot +# be used in place of another pre-release or the final version. See +# the version module for details on the version.* variable values. +# +if $version.pre_release + lib{code-seafire-common}: bin.lib.version = "-$version.project_id" +else + lib{code-seafire-common}: bin.lib.version = "-$version.major.$version.minor" + +# Install into the code/seafire/common/ subdirectory of, say, /usr/include/ +# recreating subdirectories. +# +{hxx ixx txx}{*}: +{ + install = include/code/seafire/common/ + install.subdirs = true +} diff --git a/code/seafire/common/diagnostics.cxx b/code/seafire/common/diagnostics.cxx new file mode 100644 index 0000000..bd4b481 --- /dev/null +++ b/code/seafire/common/diagnostics.cxx @@ -0,0 +1,132 @@ +#include + +#include + +namespace code::seafire::common +{ + + void + diagnostics_t:: + enable(category_t const& category) + { + categories_.emplace(&category); + } + + void + diagnostics_t:: + disable(category_t const& category) + { + categories_.erase(&category); + } + + diagnostics_t::proxy_t + diagnostics_t:: + operator<<(category_t const& category) + { + return proxy_t{*this, category}; + } + + diagnostics_t:: + diagnostics_t() = default; + + diagnostics_t:: + ~diagnostics_t() noexcept = default; + + bool + diagnostics_t:: + is_enabled(category_t const& category) + { + return categories_.count(&category) == 1; + } + + void + diagnostics_t:: + log(record_t record) + { + if (!is_enabled(record.category)) { + return; + } + + std::size_t max{0}; + + for (auto const& j : categories_) { + if (auto size = j->prefix().size(); size > max) { + max = size; + } + } + + std::stringstream message; + + for (std::string line; std::getline(record.message, line);) { + message << " -> " + << std::left + << std::setw(max) + << record.category.prefix() + << ": " + << line + << '\n'; + } + + do_log(message.str()); + } + + diagnostics_t::category_t:: + category_t(std::string prefix) + : prefix_{std::move(prefix)} + {} + + diagnostics_t::category_t:: + ~category_t() noexcept = default; + + std::string const& + diagnostics_t::category_t:: + prefix() const + { + return prefix_; + } + + diagnostics_t::proxy_t:: + ~proxy_t() noexcept + { + if (diagnostics_) { + diagnostics_->log({category_, std::move(message_)}); + } + } + + diagnostics_t::proxy_t:: + proxy_t(diagnostics_t& diagnostics, category_t const& category) + : diagnostics_{&diagnostics}, + category_{category} + {} + + diagnostics_t::proxy_t:: + proxy_t(proxy_t&& other) noexcept + : category_{other.category_} + { + std::swap(diagnostics_, other.diagnostics_); + } + + ostream_diagnostics_t:: + ostream_diagnostics_t(std::ostream& output) + : output_{output} + {} + + ostream_diagnostics_t:: + ~ostream_diagnostics_t() noexcept = default; + + std::ostream& + ostream_diagnostics_t:: + output() + { + return output_; + } + + void + ostream_diagnostics_t:: + do_log(std::string const& message) + { + output() << message; + } + + +} // namespace code::seafire::common diff --git a/code/seafire/common/diagnostics.hxx b/code/seafire/common/diagnostics.hxx new file mode 100644 index 0000000..ebeac5a --- /dev/null +++ b/code/seafire/common/diagnostics.hxx @@ -0,0 +1,160 @@ +#ifndef code__seafire__common__diagnostics_hxx_ +#define code__seafire__common__diagnostics_hxx_ + +#include +#include +#include +#include + +namespace code::seafire::common +{ + + /// Provides a base for implementing diagnostics. + /// + class diagnostics_t + { + public: + class category_t; + class proxy_t; + + friend proxy_t; + + void + enable(category_t const&); + + void + disable(category_t const&); + + proxy_t + operator<<(category_t const&); + + protected: + struct record_t; + + diagnostics_t(); + + diagnostics_t(diagnostics_t const&) = delete; + diagnostics_t(diagnostics_t&&) = delete; + + ~diagnostics_t() noexcept; + + bool + is_enabled(category_t const&); + + void + log(record_t); + + virtual + void + do_log(std::string const&) = 0; + + diagnostics_t& operator=(diagnostics_t const&) = delete; + diagnostics_t& operator=(diagnostics_t&&) = delete; + + private: + std::set categories_; + + }; + + /// Represents a diagnostic category. + /// + class diagnostics_t::category_t + { + public: + explicit + category_t(std::string); + + category_t(category_t const&) = delete; + category_t(category_t&&) = delete; + + ~category_t() noexcept; + + std::string const& + prefix() const; + + category_t& operator=(category_t const&) = delete; + category_t& operator=(category_t&&) = delete; + + private: + std::string prefix_; + + }; + + /// Proxy for writing diagnostics. + /// + class diagnostics_t::proxy_t + { + public: + ~proxy_t() noexcept; + + template + proxy_t& + operator<<(T const& other) + { + message_ << other; + return *this; + } + + protected: + friend diagnostics_t; + + proxy_t(diagnostics_t&, category_t const&); + + proxy_t(proxy_t const&) = delete; + + proxy_t(proxy_t&&) noexcept; + + proxy_t& operator=(proxy_t const&) = delete; + proxy_t& operator=(proxy_t&&) = delete; + + private: + diagnostics_t* diagnostics_{}; + category_t const& category_; + std::stringstream message_; + + }; + + /// Represents a diagnostic record. + /// + struct diagnostics_t::record_t + { + /// The diagnostic category. + /// + category_t const& category; + + /// The diagnostic message. + /// + std::stringstream message; + + }; + + class ostream_diagnostics_t + : public diagnostics_t + { + public: + explicit + ostream_diagnostics_t(std::ostream&); + + ostream_diagnostics_t(ostream_diagnostics_t const&) = delete; + ostream_diagnostics_t(ostream_diagnostics_t&&) = delete; + + ~ostream_diagnostics_t() noexcept; + + std::ostream& + output(); + + ostream_diagnostics_t& operator=(ostream_diagnostics_t const&) = delete; + ostream_diagnostics_t& operator=(ostream_diagnostics_t&&) = delete; + + protected: + void + do_log(std::string const&) override; + + private: + std::ostream& output_; + + }; + +} // namespace code::seafire::common + +#endif diff --git a/code/seafire/common/extension-context.cxx b/code/seafire/common/extension-context.cxx new file mode 100644 index 0000000..3538ba6 --- /dev/null +++ b/code/seafire/common/extension-context.cxx @@ -0,0 +1,48 @@ +#include + +namespace code::seafire::common +{ + + void* + extension_context_t:: + use(std::type_index const& key) const + { + auto it = extensions_.find(key); + + if (it == extensions_.end()) + throw extension_not_found_t{}; + + return it->second; + } + + void + extension_context_t:: + extend(std::type_index const& key, void* ptr) + { + if (0 != extensions_.count(key)) + throw duplicate_extension_t{}; + + if (!ptr) + throw std::invalid_argument{"invalid pointer"}; + + extensions_.emplace(key, ptr); + } + + void + extension_context_t:: + erase_extension(std::type_index const& key) + { + extensions_.erase(key); + } + + extension_not_found_t:: + extension_not_found_t() + : std::runtime_error{"extension not found"} + {} + + duplicate_extension_t:: + duplicate_extension_t() + : std::runtime_error{"extension already registered"} + {} + +} // namespace code::seafire::common diff --git a/code/seafire/common/extension-context.hxx b/code/seafire/common/extension-context.hxx new file mode 100644 index 0000000..8c0d7ed --- /dev/null +++ b/code/seafire/common/extension-context.hxx @@ -0,0 +1,91 @@ +#ifndef code__seafire__common__extension_context_hxx_ +#define code__seafire__common__extension_context_hxx_ + +#include +#include +#include + +namespace code::seafire::common +{ + + /// Implements a context tracking extensions. + /// + /// Ownership of registered extensions is not assumed. + /// + class extension_context_t + { + public: + template + E& + use(); + + template + E const& + use() const; + + template + void + extend(E* ptr); + + template + void + erase(); + + private: + void* + use(std::type_index const& key) const; + + void + extend(std::type_index const& key, void* ptr); + + void + erase_extension(std::type_index const& key); + + std::map extensions_; + + }; + + class extension_not_found_t + : public std::runtime_error + { + public: + extension_not_found_t(); + + }; + + class duplicate_extension_t + : public std::runtime_error + { + public: + duplicate_extension_t(); + + }; + + /// Provides extending an extension context using RAII. + /// + template + class extend_t + { + public: + using extension_type = typename std::decay_t; + + extend_t(extension_context_t&, extension_type&); + + extend_t(extend_t const&) = delete; + extend_t(extend_t&&) = delete; + + ~extend_t() noexcept; + + extend_t& operator=(extend_t const&) = delete; + extend_t& operator=(extend_t&&) = delete; + + private: + extension_context_t& target_; + + }; + +} // namespace code::seafire::common + +#include + +#endif diff --git a/code/seafire/common/extension-context.txx b/code/seafire/common/extension-context.txx new file mode 100644 index 0000000..3cbd3d3 --- /dev/null +++ b/code/seafire/common/extension-context.txx @@ -0,0 +1,63 @@ +namespace code::seafire::common +{ + + /// Use an extension of type E. + /// + template + E& + extension_context_t:: + use() + { + static std::type_index const key{typeid(E)}; + return *(static_cast(use(key))); + } + + /// Use an extension of type E. + /// + template + E const& + extension_context_t:: + use() const + { + static std::type_index const key{typeid(E)}; + return *(static_cast(use(key))); + } + + /// Add an extension to this context. + /// + template + void + extension_context_t:: + extend(E* ptr) + { + static std::type_index const key{typeid(E)}; + extend(key, ptr); + } + + /// Erase an extension from this context. + /// + template + void + extension_context_t:: + erase() + { + static std::type_index const key{typeid(E)}; + erase_extension(key); + } + + template + extend_t:: + extend_t(extension_context_t& target, extension_type& ext) + : target_{target} + { + target.extend(&ext); + } + + template + extend_t:: + ~extend_t() noexcept + { + target_.erase(); + } + +} // namespace code::seafire::common diff --git a/code/seafire/common/invoke.hxx b/code/seafire/common/invoke.hxx new file mode 100644 index 0000000..ae055d0 --- /dev/null +++ b/code/seafire/common/invoke.hxx @@ -0,0 +1,54 @@ +#ifndef code__seafire__common__invoke_hxx_ +#define code__seafire__common__invoke_hxx_ + +#include +#include + +namespace code::seafire::common +{ + + template + Ret + do_invoke(T& target, + server::request_t& req, + Direct const&... direct, + Ret (T::*func)(Direct const&..., Params const&...)); + + template + Ret + invoke(T& target, + server::request_t& req, + Ret (T::*func)(Params const&...), + Direct const&... direct); + + template + Ret + do_invoke(T const& target, + server::request_t& req, + Direct const&... direct, + Ret (T::*func)(Direct const&..., Params const&...) const); + + template + Ret + invoke(T const& target, + server::request_t& req, + Ret (T::*func)(Params const&...) const, + Direct const&... direct); + + template + Ret + do_invoke(server::request_t& req, + Direct&&... direct, + Ret (*func)(Direct..., Params...)); + + template + Ret + invoke(server::request_t& req, + Ret (*func)(Params...), + Direct&&... direct); + +} // namespace code::seafire::common + +#include + +#endif diff --git a/code/seafire/common/invoke.txx b/code/seafire/common/invoke.txx new file mode 100644 index 0000000..0b90939 --- /dev/null +++ b/code/seafire/common/invoke.txx @@ -0,0 +1,62 @@ +namespace code::seafire::common +{ + + template + Ret + do_invoke(T& target, + server::request_t& req, + Direct const&... direct, + Ret (T::*func)(Direct const&..., Params const&...)) + { + return (target.*func)(direct..., std::decay_t::fetch(req)...); + } + + template + Ret + invoke(T& target, + server::request_t& req, + Ret (T::*func)(Params const&...), + Direct const&... direct) + { + return do_invoke(target, req, direct..., func); + } + + template + Ret + do_invoke(T const& target, + server::request_t& req, + Direct const&... direct, + Ret (T::*func)(Direct const&..., Params const&...) const) + { + return (target.*func)(direct..., std::decay_t::fetch(req)...); + } + + template + Ret + invoke(T const& target, + server::request_t& req, + Ret (T::*func)(Params const&...) const, + Direct const&... direct) + { + return do_invoke(target, req, direct..., func); + } + + template + Ret + do_invoke(server::request_t& req, + Direct&&... direct, + Ret (*func)(Direct..., Params...)) + { + return (*func)(direct..., std::decay_t::fetch(req)...); + } + + template + Ret + invoke(server::request_t& req, + Ret (*func)(Params...), + Direct&&... direct) + { + return do_invoke(req, direct..., func); + } + +} // namespace code::seafire::common diff --git a/code/seafire/common/io/acceptor.cxx b/code/seafire/common/io/acceptor.cxx new file mode 100644 index 0000000..aef5d0e --- /dev/null +++ b/code/seafire/common/io/acceptor.cxx @@ -0,0 +1,12 @@ +#include + +namespace code::seafire::common::io +{ + + acceptor_t:: + ~acceptor_t() noexcept = default; + + acceptor_t:: + acceptor_t() = default; + +} // namespace code::seafire::common::io diff --git a/code/seafire/common/io/acceptor.hxx b/code/seafire/common/io/acceptor.hxx new file mode 100644 index 0000000..504ea42 --- /dev/null +++ b/code/seafire/common/io/acceptor.hxx @@ -0,0 +1,51 @@ +#ifndef code__seafire__common__io__acceptor_hxx_ +#define code__seafire__common__io__acceptor_hxx_ + +#include + +#include + +#include +#include + +namespace code::seafire::common::io +{ + + /// Abstract base class for acceptors. + /// + class acceptor_t + { + public: + /// Non-blocking accept handler type. + /// + using accept_handler_t = std::function< + void(std::error_code, std::unique_ptr) + >; + + virtual + ~acceptor_t() noexcept; + + virtual + asio::any_io_executor const& + get_executor() = 0; + + /// Initiate a non-blocking accept of a new stream. + /// + virtual + void + async_accept(accept_handler_t) = 0; + + protected: + acceptor_t(); + + acceptor_t(acceptor_t const&) = delete; + acceptor_t(acceptor_t&&) = delete; + + acceptor_t& operator=(acceptor_t const&) = delete; + acceptor_t& operator=(acceptor_t&&) = delete; + + }; + +} // namespace code::seafire::common::io + +#endif diff --git a/code/seafire/common/io/buffer.hxx b/code/seafire/common/io/buffer.hxx new file mode 100644 index 0000000..0198154 --- /dev/null +++ b/code/seafire/common/io/buffer.hxx @@ -0,0 +1,23 @@ +#ifndef code__seafire__common__io__buffer_hxx_ +#define code__seafire__common__io__buffer_hxx_ + +#include + +#include + +namespace code::seafire::common::io +{ + + using const_buffer_t = asio::const_buffer; + using const_buffers_t = std::vector; + + using mutable_buffer_t = asio::mutable_buffer; + using mutable_buffers_t = std::vector; + + using asio::buffer; + using asio::buffer_size; + using asio::buffer_copy; + +} // namespace code::seafire::common::io + +#endif diff --git a/code/seafire/common/io/diagnostics.cxx b/code/seafire/common/io/diagnostics.cxx new file mode 100644 index 0000000..48f2d56 --- /dev/null +++ b/code/seafire/common/io/diagnostics.cxx @@ -0,0 +1,13 @@ +#include + +namespace code::seafire::common::io +{ + + common::diagnostics_t::category_t const& + io_category() + { + static common::diagnostics_t::category_t category{"io"}; + return category; + } + +} // namespace code::seafire::common::io diff --git a/code/seafire/common/io/diagnostics.hxx b/code/seafire/common/io/diagnostics.hxx new file mode 100644 index 0000000..bb9223d --- /dev/null +++ b/code/seafire/common/io/diagnostics.hxx @@ -0,0 +1,14 @@ +#ifndef code__seafire__common__io_diagnostics_hxx_ +#define code__seafire__common__io_diagnostics_hxx_ + +#include + +namespace code::seafire::common::io +{ + + common::diagnostics_t::category_t const& + io_category(); + +} // namespace code::seafire::common::io + +#endif diff --git a/code/seafire/common/io/error.cxx b/code/seafire/common/io/error.cxx new file mode 100644 index 0000000..de6caa2 --- /dev/null +++ b/code/seafire/common/io/error.cxx @@ -0,0 +1,31 @@ +#include + +namespace code::seafire::common::io +{ + + std::error_code + make_error_code(error_t e) + { + static + struct : std::error_category + { + char const* name() const noexcept override + { + return "seafire.common.io"; + } + + std::string message(int e) const override + { + switch (static_cast(e)) { + case error_t::unknown: return "unknown error"; + case error_t::read_until_buffer_overflow: return "buffer overflow"; + } + + return "(unrecognized error)"; + } + } category; + + return {static_cast(e), category}; + } + +} // namespace code::seafire::common::io diff --git a/code/seafire/common/io/error.hxx b/code/seafire/common/io/error.hxx new file mode 100644 index 0000000..997de77 --- /dev/null +++ b/code/seafire/common/io/error.hxx @@ -0,0 +1,37 @@ +#ifndef code__seafire__common__io__error_hxx_ +#define code__seafire__common__io__error_hxx_ + +#include +#include + +namespace code::seafire::common::io +{ + + /// Common error codes. + /// + enum class error_t + { + /// Represents an unknown error. + /// + unknown = 1, + + read_until_buffer_overflow + + }; + + std::error_code + make_error_code(error_t); + +} // namespace code::seafire::common::io + +namespace std +{ + + template<> + struct is_error_code_enum<::code::seafire::common::io::error_t> + : true_type + {}; + +} // namespace std + +#endif diff --git a/code/seafire/common/io/read-until.cxx b/code/seafire/common/io/read-until.cxx new file mode 100644 index 0000000..f9a1d28 --- /dev/null +++ b/code/seafire/common/io/read-until.cxx @@ -0,0 +1,131 @@ +#include + +#include + +namespace code::seafire::common::io +{ + + std::size_t + read_until(stream_t& s, asio::streambuf& b, match_condition_t m) + { + std::error_code ec; + auto n = read_until(s, b, m, ec); + + if (ec) { + throw std::system_error{ec}; + } + + return n; + } + + std::size_t + read_until(stream_t& s, + asio::streambuf& b, + match_condition_t m, + std::error_code& ec) + { + for (;;) { + // search for a match. + // + auto begin = static_cast(b.data().data()); + auto result = m(begin, begin + b.data().size(), ec); + + if (ec) { + return 0; + } + + if (result.second) { + return result.first - begin; + } + + // bail out if buffer is already full. + // + if (b.size() == b.max_size()) { + ec = make_error_code(error_t::read_until_buffer_overflow); + return 0; + } + + // read more data. + // + auto bytes_to_read = std::min( + std::max( 512, b.capacity() - b.size()), + std::min(65536, b.max_size() - b.size()) + ); + + auto n = s.read(b.prepare(bytes_to_read), ec); + b.commit(n); + + if (ec) { + return 0; + } + } + } + + void + async_read_until(stream_t& s, + asio::streambuf& b, + match_condition_t m, + read_until_handler_t h) + { + struct operation_t + { + stream_t& s; + asio::streambuf& b; + match_condition_t match; + read_until_handler_t handler; + + void + operator()(std::error_code const& ec, + std::size_t n, + bool start = false) + { + if (ec || (!start && n == 0)) { + handler(ec, 0); + return; + } + + b.commit(n); + + // search for a match. + // + auto begin = static_cast(b.data().data()); + + std::error_code match_ec; + auto result = match(begin, begin + b.data().size(), match_ec); + + if (match_ec) { + handler(match_ec, 0); + return; + } + + if (result.second) { + handler({}, result.first - begin); + return; + } + + if (b.size() == b.max_size()) { + handler(make_error_code(error_t::read_until_buffer_overflow), 0); + return; + } + + // read more data. + // + auto bytes_to_read = std::min( + std::max( 512, b.capacity() - b.size()), + std::min(65536, b.max_size() - b.size()) + ); + + s.async_read(b.prepare(bytes_to_read), std::move(*this)); + } + + }; + + auto init = [&s, &b, m, h] + { + operation_t{s, b, m, h}({}, 0, true); + }; + + asio::post(s.get_executor(), init); + } + +} // namespace code::seafire::common::io diff --git a/code/seafire/common/io/read-until.hxx b/code/seafire/common/io/read-until.hxx new file mode 100644 index 0000000..7950884 --- /dev/null +++ b/code/seafire/common/io/read-until.hxx @@ -0,0 +1,39 @@ +#ifndef code__seafire__common__io__read_until_hxx_ +#define code__seafire__common__io__read_until_hxx_ + +#include + +#include +#include + +#include + +namespace code::seafire::common::io +{ + + using match_condition_t = std::function< + std::pair(char const*, char const*, std::error_code&) + >; + + using read_until_handler_t = std::function< + void(std::error_code, std::size_t) + >; + + std::size_t + read_until(stream_t&, asio::streambuf&, match_condition_t); + + std::size_t + read_until(stream_t&, + asio::streambuf&, + match_condition_t, + std::error_code&); + + void + async_read_until(stream_t&, + asio::streambuf&, + match_condition_t, + read_until_handler_t); + +} // namespace code::seafire::common::io + +#endif diff --git a/code/seafire/common/io/stream.cxx b/code/seafire/common/io/stream.cxx new file mode 100644 index 0000000..b9f9769 --- /dev/null +++ b/code/seafire/common/io/stream.cxx @@ -0,0 +1,12 @@ +#include + +namespace code::seafire::common::io +{ + + stream_t:: + ~stream_t() noexcept = default; + + stream_t:: + stream_t() = default; + +} // namespace code::seafire::common::io diff --git a/code/seafire/common/io/stream.hxx b/code/seafire/common/io/stream.hxx new file mode 100644 index 0000000..ce12de4 --- /dev/null +++ b/code/seafire/common/io/stream.hxx @@ -0,0 +1,137 @@ +#ifndef code__seafire__common__io__stream_hxx_ +#define code__seafire__common__io__stream_hxx_ + +#include + +#include + +#include +#include + +namespace code::seafire::common::io +{ + + /// Abstract base class for stream. + /// + class stream_t + { + public: + /// Non-blocking read handler type. + /// + using read_handler_t = std::function; + + /// Non-blocking write handler type. + /// + using write_handler_t = std::function; + + /// Non-blocking graceful close handler type. + /// + using graceful_close_handler_t = std::function; + + virtual + ~stream_t() noexcept; + + /// Get the asio executor associated with this stream. + /// + virtual + asio::any_io_executor const& + get_executor() = 0; + + /// Cancel any pending non-blocking operations. + /// + virtual + void + cancel() = 0; + + /// Close this stream. + /// + virtual + void + close() = 0; + + /// Close this stream. + /// + virtual + void + close(std::error_code&) = 0; + + /// Perform a blocking graceful close of this stream. + /// + virtual + void + graceful_close() = 0; + + /// Initiate a non-blocking graceful close of this stream. + /// + virtual + void + async_graceful_close(graceful_close_handler_t) = 0; + + /// Perform a blocking read on this stream. + /// + virtual + std::size_t + read(mutable_buffer_t const&) = 0; + + /// Perform a blocking read on this stream. + /// + virtual + std::size_t + read(mutable_buffer_t const&, std::error_code&) = 0; + + /// Initiate a non-blocking read on this stream. + /// + virtual + void + async_read(mutable_buffer_t const&, read_handler_t) = 0; + + /// Perform a blocking write on this stream. + /// + virtual + std::size_t + write(const_buffer_t const&) = 0; + + /// Perform a blocking write on this stream. + /// + virtual + std::size_t + write(const_buffers_t const&) = 0; + + /// Initiate a non-block write on this stream. + /// + virtual + std::size_t + write(const_buffer_t const&, std::error_code&) = 0; + + /// Initiate a non-block write on this stream. + /// + virtual + std::size_t + write(const_buffers_t const&, std::error_code&) = 0; + + /// Initiate a non-blocking write on this stream. + /// + virtual + void + async_write(const_buffer_t const&, write_handler_t) = 0; + + /// Initiate a non-blocking write on this stream. + /// + virtual + void + async_write(const_buffers_t const&, write_handler_t) = 0; + + protected: + stream_t(); + + stream_t(stream_t const&) = delete; + stream_t(stream_t&&) = delete; + + stream_t& operator=(stream_t const&) = delete; + stream_t& operator=(stream_t&&) = delete; + + }; + +} // namespace code::seafire::common::io + +#endif diff --git a/code/seafire/common/io/tcp-acceptor.cxx b/code/seafire/common/io/tcp-acceptor.cxx new file mode 100644 index 0000000..1eff3f2 --- /dev/null +++ b/code/seafire/common/io/tcp-acceptor.cxx @@ -0,0 +1,36 @@ +#include +#include + + +namespace code::seafire::common::io +{ + + tcp_acceptor_t:: + tcp_acceptor_t(asio::io_context& io_context, + asio::ip::tcp::endpoint const& endpoint) + : acceptor_{io_context, endpoint} + {} + + tcp_acceptor_t:: + ~tcp_acceptor_t() noexcept = default; + + asio::any_io_executor const& + tcp_acceptor_t:: + get_executor() + { + return acceptor_.get_executor(); + } + + void + tcp_acceptor_t:: + async_accept(accept_handler_t handler) + { + auto bound = [handler](std::error_code const& ec, asio::ip::tcp::socket socket) + { + handler(ec, std::make_unique(std::move(socket))); + }; + + acceptor_.async_accept(bound); + } + +} // namespace code::seafire::common::io diff --git a/code/seafire/common/io/tcp-acceptor.hxx b/code/seafire/common/io/tcp-acceptor.hxx new file mode 100644 index 0000000..f13039f --- /dev/null +++ b/code/seafire/common/io/tcp-acceptor.hxx @@ -0,0 +1,43 @@ +#ifndef code__seafire__common__io__tcp_acceptor_hxx_ +#define code__seafire__common__io__tcp_acceptor_hxx_ + +#include + +#include + +#include + +namespace code::seafire::common::io +{ + + /// Implements an tcp acceptor. + /// + class tcp_acceptor_t + : public acceptor_t + { + public: + tcp_acceptor_t(asio::io_context&, + asio::ip::tcp::endpoint const&); + + tcp_acceptor_t(tcp_acceptor_t const&) = delete; + tcp_acceptor_t(tcp_acceptor_t&&) = delete; + + ~tcp_acceptor_t() noexcept override; + + asio::any_io_executor const& + get_executor() override; + + void + async_accept(accept_handler_t) override; + + tcp_acceptor_t& operator=(tcp_acceptor_t const&) = delete; + tcp_acceptor_t& operator=(tcp_acceptor_t&&) = delete; + + private: + asio::ip::tcp::acceptor acceptor_; + + }; + +} // namespace code::seafire::common::io + +#endif diff --git a/code/seafire/common/io/tcp-socket.cxx b/code/seafire/common/io/tcp-socket.cxx new file mode 100644 index 0000000..0a1ef21 --- /dev/null +++ b/code/seafire/common/io/tcp-socket.cxx @@ -0,0 +1,149 @@ +#include + +namespace code::seafire::common::io +{ + + /// Construct a new TCP/IP socket from an asio socket. + /// + tcp_socket_t:: + tcp_socket_t(asio::ip::tcp::socket socket) + : socket_{std::move(socket)} + {} + + tcp_socket_t:: + ~tcp_socket_t() noexcept = default; + + asio::any_io_executor const& + tcp_socket_t:: + get_executor() + { + return socket_.get_executor(); + } + + void + tcp_socket_t:: + cancel() + { + socket_.cancel(); + } + + void + tcp_socket_t:: + close() + { + socket_.close(); + } + + void + tcp_socket_t:: + close(std::error_code& ec) + { + socket_.close(ec); + } + + void + tcp_socket_t:: + graceful_close() + { + try { + socket_.shutdown(asio::ip::tcp::socket::shutdown_send); + socket_.read_some(buffer(throwaway_)); + } + catch (...) { + // Ignore errors during shutdown/close. + // + } + } + + void + tcp_socket_t:: + async_graceful_close(graceful_close_handler_t handler) + { + std::error_code ec; + socket_.shutdown(asio::ip::tcp::socket::shutdown_send, ec); + + if (ec) { + auto bound = [handler]() + { + handler(); + }; + + asio::post(get_executor(), bound); + return; + } + + auto bound = [handler](std::error_code const&, std::size_t) + { + // Errors are ignored during graceful shutdown/close, so we + // don't pass anything to the handler. + // + handler(); + }; + + socket_.async_read_some(buffer(throwaway_), bound); + } + + std::size_t + tcp_socket_t:: + read(mutable_buffer_t const& buffer) + { + return socket_.read_some(buffer); + } + + std::size_t + tcp_socket_t:: + read(mutable_buffer_t const& buffer, std::error_code& ec) + { + return socket_.read_some(buffer, ec); + } + + void + tcp_socket_t:: + async_read(mutable_buffer_t const& buffer, read_handler_t handler) + { + return socket_.async_read_some(buffer, handler); + } + + std::size_t + tcp_socket_t:: + write(const_buffer_t const& buffer) + { + return asio::write(socket_, buffer); + } + + std::size_t + tcp_socket_t:: + write(const_buffers_t const& buffers) + { + return asio::write(socket_, buffers); + } + + std::size_t + tcp_socket_t:: + write(const_buffer_t const& buffer, std::error_code& ec) + { + return asio::write(socket_, buffer, ec); + } + + std::size_t + tcp_socket_t:: + write(const_buffers_t const& buffers, std::error_code& ec) + { + return asio::write(socket_, buffers, ec); + } + + void + tcp_socket_t:: + async_write(const_buffer_t const& buffer, write_handler_t handler) + { + asio::async_write(socket_, buffer, handler); + } + + void + tcp_socket_t:: + async_write(const_buffers_t const& buffers, write_handler_t handler) + { + asio::async_write(socket_, buffers, handler); + } + +} // namespace code::seafire::common::io diff --git a/code/seafire/common/io/tcp-socket.hxx b/code/seafire/common/io/tcp-socket.hxx new file mode 100644 index 0000000..dca0327 --- /dev/null +++ b/code/seafire/common/io/tcp-socket.hxx @@ -0,0 +1,82 @@ +#ifndef code__seafire__common__io__tcp_socket_hxx_ +#define code__seafire__common__io__tcp_socket_hxx_ + +#include +#include + +#include + +namespace code::seafire::common::io +{ + + /// Implements a TCP/IP socket. + /// + class tcp_socket_t + : public stream_t + { + public: + explicit + tcp_socket_t(asio::ip::tcp::socket); + + tcp_socket_t(tcp_socket_t const&) = delete; + tcp_socket_t(tcp_socket_t&&) = delete; + + ~tcp_socket_t() noexcept override; + + asio::any_io_executor const& + get_executor() override; + + void + cancel() override; + + void + close() override; + + void + close(std::error_code&) override; + + void + graceful_close() override; + + void + async_graceful_close(graceful_close_handler_t) override; + + std::size_t + read(mutable_buffer_t const&) override; + + std::size_t + read(mutable_buffer_t const&, std::error_code&) override; + + void + async_read(mutable_buffer_t const&, read_handler_t) override; + + std::size_t + write(const_buffer_t const&) override; + + std::size_t + write(const_buffers_t const&) override; + + std::size_t + write(const_buffer_t const&, std::error_code&) override; + + std::size_t + write(const_buffers_t const&, std::error_code&) override; + + void + async_write(const_buffer_t const&, write_handler_t) override; + + void + async_write(const_buffers_t const&, write_handler_t) override; + + tcp_socket_t& operator=(tcp_socket_t const&) = delete; + tcp_socket_t& operator=(tcp_socket_t&&) = delete; + + private: + asio::ip::tcp::socket socket_; + char throwaway_[1]; + + }; + +} // namespace code::seafire::common::io + +#endif diff --git a/code/seafire/common/traits.hxx b/code/seafire/common/traits.hxx new file mode 100644 index 0000000..af5733f --- /dev/null +++ b/code/seafire/common/traits.hxx @@ -0,0 +1,162 @@ +#ifndef code__seafire__common__traits_hxx_ +#define code__seafire__common__traits_hxx_ + +#include +#include +#include + +namespace code::seafire::common::traits +{ + + // is_optional + // + + template + struct is_optional + : std::false_type + {}; + + template + struct is_optional> + : std::true_type + {}; + + template + constexpr bool is_optional_v{is_optional::value}; + + // remove_optional + // + + template + struct remove_optional + { + using type = T; + }; + + template + struct remove_optional> + { + using type = T; + }; + + template + using remove_optional_t = remove_optional::type; + + // add_optional + // + + template + std::optional> + add_optional(remove_optional&& non_optional) + { + return std::optional>{non_optional}; + } + + // function_traits + // + + template + struct function_traits; + + template + struct function_traits + { + static constexpr std::size_t arity = sizeof...(Args); + using return_type = std::decay_t; + using argument_tuple = std::tuple...>; + }; + + template + struct function_traits + { + static constexpr std::size_t arity = sizeof...(Args); + using return_type = std::decay_t; + using class_type = Class; + using argument_tuple = std::tuple...>; + }; + + template + struct function_traits + { + static constexpr std::size_t arity = sizeof...(Args); + using return_type = std::decay_t; + using class_type = Class; + using argument_tuple = std::tuple...>; + }; + + template + struct function_traits + { + static constexpr std::size_t arity = sizeof...(Args); + using return_type = std::decay_t; + using argument_tuple = std::tuple...>; + }; + + // return_type_t + // + + template + using return_type_t = typename function_traits::return_type; + + // first_arg + // + + template + struct first_arg + { + using function_traits = traits::function_traits; + using type = std::tuple_element_t<0, typename function_traits::argument_tuple>; + }; + + template + using first_arg_t = first_arg::type; + + // function_arg_n + // + + template< + typename F, + std::size_t arg, + std::size_t arity = function_traits::arity + > + struct function_arg_n; + + template< + typename F, + std::size_t arg + > + struct function_arg_n + { + using function_traits = traits::function_traits; + using type = void; + }; + + template< + typename F, + std::size_t arg + > + struct function_arg_n + { + using function_traits = traits::function_traits; + using type = std::tuple_element_t; + }; + + template< + typename F, + std::size_t arg + > + struct function_arg_n + { + using function_traits = traits::function_traits; + using type = std::tuple_element_t; + }; + + template< + typename F, + std::size_t arg + > + using function_arg_n_t = function_arg_n::type; + +} // namespace code::seafire::common::traits + +#endif diff --git a/code/seafire/common/version.hxx.in b/code/seafire/common/version.hxx.in new file mode 100644 index 0000000..25c7e41 --- /dev/null +++ b/code/seafire/common/version.hxx.in @@ -0,0 +1,37 @@ +#ifndef code__seafire__common__version_hxx_ +#define code__seafire__common__version_hxx_ + +// The numeric version format is AAAAABBBBBCCCCCDDDE where: +// +// AAAAA - major version number +// BBBBB - minor version number +// CCCCC - bugfix version number +// DDD - alpha / beta (DDD + 500) version number +// E - final (0) / snapshot (1) +// +// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example: +// +// Version AAAAABBBBBCCCCCDDDE +// +// 0.1.0 0000000001000000000 +// 0.1.2 0000000001000020000 +// 1.2.3 0000100002000030000 +// 2.2.0-a.1 0000200001999990010 +// 3.0.0-b.2 0000299999999995020 +// 2.2.0-a.1.z 0000200001999990011 +// +#define LIBCODE_SEAFIRE_COMMON_VERSION $libcode_seafire_common.version.project_number$ULL +#define LIBCODE_SEAFIRE_COMMON_VERSION_STR "$libcode_seafire_common.version.project$" +#define LIBCODE_SEAFIRE_COMMON_VERSION_ID "$libcode_seafire_common.version.project_id$" +#define LIBCODE_SEAFIRE_COMMON_VERSION_FULL "$libcode_seafire_common.version$" + +#define LIBCODE_SEAFIRE_COMMON_VERSION_MAJOR $libcode_seafire_common.version.major$ +#define LIBCODE_SEAFIRE_COMMON_VERSION_MINOR $libcode_seafire_common.version.minor$ +#define LIBCODE_SEAFIRE_COMMON_VERSION_PATCH $libcode_seafire_common.version.patch$ + +#define LIBCODE_SEAFIRE_COMMON_PRE_RELEASE $libcode_seafire_common.version.pre_release$ + +#define LIBCODE_SEAFIRE_COMMON_SNAPSHOT_SN $libcode_seafire_common.version.snapshot_sn$ULL +#define LIBCODE_SEAFIRE_COMMON_SNAPSHOT_ID "$libcode_seafire_common.version.snapshot_id$" + +#endif diff --git a/manifest b/manifest new file mode 100644 index 0000000..66d225c --- /dev/null +++ b/manifest @@ -0,0 +1,12 @@ +: 1 +name: libcode-seafire-common +version: 0.1.0-a.0.z +language: c++ +summary: libcode-seafire-common C++ library +license: BSD-4-Clause +description-file: README.md +url: https://helloryan.se/code/ +email: ryan@helloryan.se +depends: * build2 >= 0.17.0 +depends: * bpkg >= 0.17.0 +depends: libasio ^1.29.0 diff --git a/repositories.manifest b/repositories.manifest new file mode 100644 index 0000000..d7cf08e --- /dev/null +++ b/repositories.manifest @@ -0,0 +1,7 @@ +: 1 +summary: libcode-seafire-common project repository + +: +role: prerequisite +location: https://pkg.cppget.org/1/beta +trust: 70:64:FE:E4:E0:F3:60:F1:B4:51:E1:FA:12:5C:E0:B3:DB:DF:96:33:39:B9:2E:E5:C2:68:63:4C:A6:47:39:43 diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..662178d --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,8 @@ +# Test executables. +# +driver + +# Testscript output directories (can be symlinks). +# +test +test-* diff --git a/tests/build/.gitignore b/tests/build/.gitignore new file mode 100644 index 0000000..974e01d --- /dev/null +++ b/tests/build/.gitignore @@ -0,0 +1,4 @@ +/config.build +/root/ +/bootstrap/ +build/ diff --git a/tests/build/bootstrap.build b/tests/build/bootstrap.build new file mode 100644 index 0000000..a07b5ea --- /dev/null +++ b/tests/build/bootstrap.build @@ -0,0 +1,5 @@ +project = # Unnamed tests subproject. + +using config +using test +using dist diff --git a/tests/build/root.build b/tests/build/root.build new file mode 100644 index 0000000..a67b2fe --- /dev/null +++ b/tests/build/root.build @@ -0,0 +1,16 @@ +cxx.std = latest + +using cxx + +hxx{*}: extension = hxx +ixx{*}: extension = ixx +txx{*}: extension = txx +cxx{*}: extension = cxx + +# Every exe{} in this subproject is by default a test. +# +exe{*}: test = true + +# The test target for cross-testing (running tests under Wine, etc). +# +test.target = $cxx.target diff --git a/tests/buildfile b/tests/buildfile new file mode 100644 index 0000000..aeeab15 --- /dev/null +++ b/tests/buildfile @@ -0,0 +1 @@ +./: {*/ -build/}