diff --git a/.github/.licenserc.yaml b/.github/.licenserc.yaml index a667c903e..dcfcd64f6 100644 --- a/.github/.licenserc.yaml +++ b/.github/.licenserc.yaml @@ -29,6 +29,7 @@ header: - 'LICENSE' - 'NOTICE' - 'requirements.txt' + - 'src/iceberg/expected.h' - 'src/iceberg/util/murmurhash3_internal.*' - 'src/iceberg/test/resources/**' - 'src/iceberg/catalog/hive/gen-cpp/**' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 352da0d7a..524693b87 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,12 +78,19 @@ jobs: with: key-prefix: sccache-test-ubuntu-${{ matrix.cmake_build_type }} job-status: ${{ job.status }} - - name: Build Example + - name: Build Example (C++20) shell: bash env: CC: gcc-14 CXX: g++-14 run: ci/scripts/build_example.sh $(pwd)/example + - name: Build Example (C++23) + shell: bash + env: + CC: gcc-14 + CXX: g++-14 + ICEBERG_EXAMPLE_CXX_STANDARD: 23 + run: ci/scripts/build_example.sh $(pwd)/example hive: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} name: AMD64 Ubuntu 26.04 Hive @@ -153,9 +160,14 @@ jobs: with: key-prefix: sccache-test-macos job-status: ${{ job.status }} - - name: Build Example + - name: Build Example (C++20) shell: bash run: ci/scripts/build_example.sh $(pwd)/example + - name: Build Example (C++23) + shell: bash + env: + ICEBERG_EXAMPLE_CXX_STANDARD: 23 + run: ci/scripts/build_example.sh $(pwd)/example windows: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} name: AMD64 Windows 2025 @@ -199,8 +211,15 @@ jobs: with: key-prefix: sccache-test-windows job-status: ${{ job.status }} - - name: Build Example + - name: Build Example (C++20) + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + bash -lc 'ci/scripts/build_example.sh $(pwd)/example' + - name: Build Example (C++23) shell: pwsh + env: + ICEBERG_EXAMPLE_CXX_STANDARD: 23 run: | $ErrorActionPreference = "Stop" bash -lc 'ci/scripts/build_example.sh $(pwd)/example' diff --git a/LICENSE b/LICENSE index d0429e24b..6d0d88e91 100644 --- a/LICENSE +++ b/LICENSE @@ -230,6 +230,34 @@ License: https://www.apache.org/licenses/LICENSE-2.0 -------------------------------------------------------------------------------- +This product includes code from zeus-cpp/expected. + +* src/iceberg/expected.h is adapted from zeus-cpp/expected. + +Copyright: 2024 zeus-cpp. +Home page: https://github.com/zeus-cpp/expected +License: MIT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + This product bundles utf8proc, which is available under the MIT License: utf8proc is a software package originally developed by Jan Behrens and the rest diff --git a/README.md b/README.md index 920b01a59..9fc61ad43 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,14 @@ C++ implementation of [Apache Iceberg™](https://iceberg.apache.org/). **Required:** -- C++23 compliant compiler (GCC 14+, Clang 18+, MSVC 2022+) +- C++23 compliant compiler (GCC 14+, Clang 18+, MSVC 2022+) to build iceberg-cpp itself - CMake 3.25+ - [Ninja](https://ninja-build.org/) (recommended build backend) +**Using iceberg-cpp from your project:** the installed public headers require +C++20 at minimum, so applications that link against iceberg-cpp can be +compiled as C++20 or later. The library itself is still built as C++23. + **Optional:** - Python 3 and [pre-commit](https://pre-commit.com/) (for linting) @@ -121,6 +125,14 @@ If you are using provided Apache Arrow, include `/path/to/arrow` in `CMAKE_PREFI cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH="/path/to/install;/path/to/arrow" ``` +The examples build as C++20 by default, which is the minimum standard supported +by the public headers. Set `ICEBERG_EXAMPLE_CXX_STANDARD` to `23` to build them +as C++23 instead: + +```bash +cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/install -DICEBERG_EXAMPLE_CXX_STANDARD=23 +``` + ## Customizing Dependency URLs If you experience network issues when downloading dependencies, you can customize the download URLs using environment variables: diff --git a/ci/scripts/build_example.sh b/ci/scripts/build_example.sh index 7ccdf4a8f..386019ad8 100755 --- a/ci/scripts/build_example.sh +++ b/ci/scripts/build_example.sh @@ -22,6 +22,7 @@ set -eux source_dir=${1} build_dir=${1}/build run_example=${ICEBERG_RUN_EXAMPLE:-OFF} +cxx_standard=${ICEBERG_EXAMPLE_CXX_STANDARD:-20} # Clean up before configuring. If Windows still holds a just-built exe/dll # after the retries, let mkdir fail rather than reuse a half-deleted tree. @@ -53,6 +54,7 @@ fi build_type="${ICEBERG_BUILD_TYPE:-Debug}" CMAKE_ARGS+=("-DCMAKE_BUILD_TYPE=${build_type}") +CMAKE_ARGS+=("-DICEBERG_EXAMPLE_CXX_STANDARD=${cxx_standard}") cmake "${CMAKE_ARGS[@]}" ${source_dir} cmake --build . diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index a9bf73cf0..44c599cf8 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -20,13 +20,79 @@ cmake_minimum_required(VERSION 3.25) project(example) -set(CMAKE_CXX_STANDARD 23) +# C++20 is the minimum standard iceberg-cpp's public headers support, so the +# example builds as C++20 by default to keep that contract exercised. Set this to +# 23 to also check the headers from a C++23 consumer. +set(ICEBERG_EXAMPLE_CXX_STANDARD + 20 + CACHE STRING "C++ standard used to build the example (20 or 23)") +set_property(CACHE ICEBERG_EXAMPLE_CXX_STANDARD PROPERTY STRINGS 20 23) +if(NOT ICEBERG_EXAMPLE_CXX_STANDARD MATCHES "^(20|23)$") + message(FATAL_ERROR "ICEBERG_EXAMPLE_CXX_STANDARD must be 20 or 23, got " + "'${ICEBERG_EXAMPLE_CXX_STANDARD}'") +endif() + +set(CMAKE_CXX_STANDARD ${ICEBERG_EXAMPLE_CXX_STANDARD}) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) find_package(iceberg CONFIG REQUIRED COMPONENTS bundle rest) +if(TARGET iceberg::iceberg_bundle_shared) + set(ICEBERG_BUNDLE_TARGET iceberg::iceberg_bundle_shared) +else() + set(ICEBERG_BUNDLE_TARGET iceberg::iceberg_bundle_static) +endif() + +if(TARGET iceberg::iceberg_rest_shared) + set(ICEBERG_REST_TARGET iceberg::iceberg_rest_shared) +else() + set(ICEBERG_REST_TARGET iceberg::iceberg_rest_static) +endif() + add_executable(demo_example demo_example.cc) -target_link_libraries(demo_example - PRIVATE "$,iceberg::iceberg_bundle_shared,iceberg::iceberg_bundle_static>" - "$,iceberg::iceberg_rest_shared,iceberg::iceberg_rest_static>" -) +target_link_libraries(demo_example PRIVATE ${ICEBERG_BUNDLE_TARGET} + ${ICEBERG_REST_TARGET}) + +# Compile every installed public header as a consumer using +# ICEBERG_EXAMPLE_CXX_STANDARD. The installed include +# tree is the public API contract: iceberg_install_all_headers excludes internal +# headers before packaging them. +get_target_property(ICEBERG_BUNDLE_INCLUDE_DIRS ${ICEBERG_BUNDLE_TARGET} + INTERFACE_INCLUDE_DIRECTORIES) +foreach(ICEBERG_INCLUDE_DIR IN LISTS ICEBERG_BUNDLE_INCLUDE_DIRS) + if(EXISTS "${ICEBERG_INCLUDE_DIR}/iceberg") + set(ICEBERG_PUBLIC_INCLUDE_DIR "${ICEBERG_INCLUDE_DIR}") + break() + endif() +endforeach() + +if(NOT ICEBERG_PUBLIC_INCLUDE_DIR) + message(FATAL_ERROR "Could not locate iceberg's installed public headers") +endif() + +file(GLOB_RECURSE + ICEBERG_PUBLIC_HEADERS + CONFIGURE_DEPENDS + "${ICEBERG_PUBLIC_INCLUDE_DIR}/iceberg/*.h" + "${ICEBERG_PUBLIC_INCLUDE_DIR}/iceberg/*.hpp") +list(SORT ICEBERG_PUBLIC_HEADERS) + +set(ICEBERG_PUBLIC_HEADER_CHECK_SOURCE + "// Generated from iceberg's installed public headers.\n") +foreach(ICEBERG_PUBLIC_HEADER IN LISTS ICEBERG_PUBLIC_HEADERS) + file(RELATIVE_PATH ICEBERG_PUBLIC_HEADER_RELATIVE_PATH "${ICEBERG_PUBLIC_INCLUDE_DIR}" + "${ICEBERG_PUBLIC_HEADER}") + string(APPEND ICEBERG_PUBLIC_HEADER_CHECK_SOURCE + "#include <${ICEBERG_PUBLIC_HEADER_RELATIVE_PATH}>\n") +endforeach() +string(APPEND ICEBERG_PUBLIC_HEADER_CHECK_SOURCE "\nint main() { return 0; }\n") + +file(GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/public_headers_check.cc" + CONTENT "${ICEBERG_PUBLIC_HEADER_CHECK_SOURCE}") + +add_executable(public_headers_check "${CMAKE_CURRENT_BINARY_DIR}/public_headers_check.cc") +target_link_libraries(public_headers_check PRIVATE ${ICEBERG_BUNDLE_TARGET} + ${ICEBERG_REST_TARGET}) diff --git a/mkdocs/docs/getting-started.md b/mkdocs/docs/getting-started.md index 89ffd0a0b..fea517e3a 100644 --- a/mkdocs/docs/getting-started.md +++ b/mkdocs/docs/getting-started.md @@ -23,10 +23,14 @@ **Required:** -- C++23 compliant compiler (GCC 14+, Clang 18+, MSVC 2022+) +- C++23 compliant compiler (GCC 14+, Clang 18+, MSVC 2022+) to build iceberg-cpp itself - CMake 3.25+ - [Ninja](https://ninja-build.org/) (recommended build backend) +**Using iceberg-cpp from your project:** the installed public headers require +C++20 at minimum, so applications that link against iceberg-cpp can be +compiled as C++20 or later. The library itself is still built as C++23. + ## Quick Start ```bash @@ -112,6 +116,14 @@ If using provided Apache Arrow, include both paths: cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH="/path/to/install;/path/to/arrow" ``` +The examples build as C++20 by default, which is the minimum standard supported +by the public headers. Set `ICEBERG_EXAMPLE_CXX_STANDARD` to `23` to build them +as C++23 instead: + +```bash +cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/install -DICEBERG_EXAMPLE_CXX_STANDARD=23 +``` + ## Customizing Dependency URLs If you experience network issues when downloading dependencies, you can override the download URLs using environment variables: diff --git a/mkdocs/docs/index.md b/mkdocs/docs/index.md index 499583bad..73d92b283 100644 --- a/mkdocs/docs/index.md +++ b/mkdocs/docs/index.md @@ -25,7 +25,7 @@ iceberg-cpp is a C++ implementation of [Apache Iceberg™](https://iceberg.apach ## Key Features -- **Modern C++23** — Built with ranges, concepts, `std::expected`, and other modern idioms +- **Modern C++** — Built as C++23 with ranges, concepts, and other modern idioms; public headers require only C++20 - **Cross-Platform** — Builds and runs on Linux, macOS, and Windows - **Spec Compliance** — Full table spec support today; Puffin, View, and UDF specs are on the roadmap - **Arrow-Native** — Uses the Arrow C Data Interface as the primary data API diff --git a/src/iceberg/expected.h b/src/iceberg/expected.h new file mode 100644 index 000000000..f79b8ce55 --- /dev/null +++ b/src/iceberg/expected.h @@ -0,0 +1,2443 @@ +/* + * MIT License + * + * Copyright (c) 2024 zeus-cpp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/// \file iceberg/expected.h +/// \brief A C++20 backport of C++23 `std::expected`, used as the storage type +/// behind `iceberg::Result`. +/// +/// Purpose: the library itself may be built as C++23, but its public headers +/// must stay consumable from C++20 translation units. `std::expected` is a +/// C++23 library feature, so `iceberg::Result` (see iceberg/result.h) is +/// defined in terms of `iceberg::expected` instead. The API mirrors +/// `std::expected` (including the monadic `and_then`, `or_else`, `transform` +/// and `transform_error`), so code can be written as if against the standard +/// type. +/// +/// History: +/// - apache/iceberg-cpp#40 vendored this header, adapted from +/// https://github.com/zeus-cpp/expected (MIT), while the project targeted +/// C++20. +/// - apache/iceberg-cpp#139 raised the project to C++23 and removed the header +/// in favor of `std::expected`, which made C++23 a requirement for users of +/// the public headers too. +/// - apache/iceberg-cpp#936 restored it so the public headers work in C++20 +/// again, with these changes on top of the #40 version: +/// * The default constructor no longer requires `T` to be default +/// constructible, and the converting constructors start from `no_init` +/// rather than default constructing `T` first. This lets +/// `expected` hold types with no (or a private) default constructor, +/// which MSVC otherwise tried to instantiate. +/// * `expected` is marked `ICEBERG_TEMPLATE_CLASS_EXPORT` rather than +/// `ICEBERG_EXPORT`, so exporting a class template does not break the +/// Windows build. +/// * When `std::expected` is available, `iceberg::expected` can also be +/// constructed from `std::unexpected`, so library code compiled as C++23 +/// that still spells errors with `std::unexpected` keeps working. +/// +/// Once C++23 can be required of all consumers, this header can be dropped and +/// `iceberg::Result` aliased back to `std::expected`. + +#pragma once + +#include +#include +#include +#include +#include +#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L +# include +#endif + +#include "iceberg/iceberg_export.h" + +// NOLINTBEGIN + +namespace iceberg { + +namespace expected_detail { + +template class Template> +inline constexpr bool is_specialization_v = + false; // true if and only if T is a specialization of Template +template