Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
name: Meson - ${{ matrix.title }}
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 30
timeout-minutes: 60
strategy:
max-parallel: 15
fail-fast: false
Expand All @@ -221,9 +221,22 @@ jobs:
meson-setup-args: -Drest_integration_test=enabled
- title: AMD64 Windows 2025
runs-on: windows-2025
meson-setup-args: --vsenv
meson-setup-args: --vsenv --buildtype=release --cmake-prefix-path=C:/vcpkg/installed/x64-windows-static-md
- title: AArch64 macOS 26
runs-on: macos-26
- title: AMD64 Ubuntu 26.04, all catalogs, S3 and library variants
runs-on: ubuntu-26.04
CC: gcc-14
CXX: g++-14
sql-clients: true
benchmarks: true
meson-setup-args: --default-library=both --buildtype=release -Db_ndebug=true --force-fallback-for=arrow,avro -Dhive=enabled -Dsql_catalog=enabled -Dsql_sqlite=enabled -Dsql_postgresql=enabled -Dsql_mysql=enabled -Dbenchmarks=enabled -Ds3=enabled -Dsigv4=enabled
- title: AMD64 Ubuntu 26.04, bundle disabled
runs-on: ubuntu-26.04
CC: gcc-14
CXX: g++-14
bundle: disabled
meson-setup-args: -Dbundle=disabled
steps:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
Expand All @@ -244,13 +257,32 @@ jobs:
run: |
echo "CC=sccache ${{ matrix.CC }}" >> $GITHUB_ENV
echo "CXX=sccache ${{ matrix.CXX }}" >> $GITHUB_ENV
- name: Install SQL client development packages
if: ${{ matrix.sql-clients }}
run: |
# Match the PostgreSQL packages preinstalled from PGDG on the runner.
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
sudo apt-get install -y libsqlite3-dev libpq-dev "postgresql-server-dev-$(pg_config --version | awk '{print $2}' | cut -d. -f1)" default-libmysqlclient-dev libcurl4-openssl-dev libgpg-error-dev libunistring-dev libsasl2-dev libdb-dev
echo "LIBRARY_PATH=$(pg_config --pkglibdir)" >> "$GITHUB_ENV"
# Complete the system packages' static link interfaces.
sudo sed -i '/^Requires.private:/s/$/ libsasl2/' "$(pkg-config --variable=pcfiledir libcurl)/libcurl.pc"
sudo sed -i '/^Libs.private:/s/$/ -ldb -lsqlite3 -lpq -lmysqlclient/' "$(pkg-config --variable=pcfiledir libsasl2)/libsasl2.pc"
sudo sed -i 's/-lpgcommon /-lpgcommon_shlib /' "$(pkg-config --variable=pcfiledir libpq)/libpq.pc"
- name: Install OpenSSL static dependencies
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libjitterentropy3-dev
- name: Install Zlib and pkgconf
if: runner.os == 'Windows'
run: |
vcpkg install zlib:x64-windows-static-md pkgconf:x64-windows-static-md
"PKG_CONFIG=C:/vcpkg/installed/x64-windows-static-md/tools/pkgconf/pkgconf.exe" >> $env:GITHUB_ENV
- name: Set up sccache
uses: ./.github/actions/setup-sccache
with:
key-prefix: sccache-meson-${{ matrix.runs-on }}
- name: Build Iceberg
run: |
meson setup builddir ${{ matrix.meson-setup-args || '' }}
meson setup builddir ${{ matrix.meson-setup-args || '' }} --prefix="${{ github.workspace }}/install" --libdir=lib
meson compile -C builddir
- name: Save sccache
if: always()
Expand All @@ -261,3 +293,8 @@ jobs:
- name: Test Iceberg
run: |
meson test -C builddir --timeout-multiplier 0 --print-errorlogs
- name: Test installed Meson libraries
run: python ci/scripts/test_meson_install.py builddir --bundle=${{ matrix.bundle || 'enabled' }}
- name: Test Meson benchmarks
if: ${{ matrix.benchmarks }}
run: meson test -C builddir --benchmark --print-errorlogs
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ C++ implementation of [Apache Iceberg™](https://iceberg.apache.org/).
**Required:**

- C++23 compliant compiler (GCC 14+, Clang 18+, MSVC 2022+)
- CMake 3.25+ or Meson 1.5+
- CMake 3.25+ or Meson 1.8.3+
- [Ninja](https://ninja-build.org/) (recommended build backend)

**Optional:**
Expand Down Expand Up @@ -113,6 +113,31 @@ meson compile -C builddir
meson test -C builddir --timeout-multiplier 0
```

Meson builds `iceberg_bundle` by default, including Arrow filesystem, Avro,
and Parquet support. Use `-Dbundle=disabled` to omit it.

System dependencies are preferred. System Arrow and Parquet are resolved through
their CMake packages; separate Arrow component pkg-config files are not required.
Arrow, Avro, CRoaring, and sqlpp23 source fallbacks use upstream CMake. Use
`--force-fallback-for=arrow,avro` to force Arrow and Avro source builds.

Enable S3 with `-Ds3=enabled`. System Arrow must include S3 support; the Arrow
source fallback bundles AWS SDK by default.
On Linux, the bundled SDK uses system OpenSSL for s2n TLS support.
Use `-Dbundle_awssdk=false` to use an installed AWS SDK in the Arrow source build.
When S3 bundles AWS SDK, `-Dsigv4=enabled` reuses it for REST authentication;
SigV4 without S3 requires an installed AWS SDK.

Static libraries are the default. Installed libraries are available through
pkg-config, for example `dependency('iceberg_bundle')`. With
`--default-library=both`, use `dependency('iceberg_bundle_static', static: true)`
or `dependency('iceberg_bundle_shared', static: false)` to select a variant;
the same suffixes and `static` argument apply to other modules.

Enable Hive with `-Dhive=enabled`. It reuses Thrift from the Arrow source fallback
by default; use `-Dbundle_thrift=false` for system Thrift. SQL connectors require
their native client development packages.

Meson provides built-in equivalents for several CMake options:

- `--default-library=<shared|static|both>` instead of `ICEBERG_BUILD_STATIC` / `ICEBERG_BUILD_SHARED`
Expand All @@ -123,7 +148,16 @@ Meson-specific options (configured via `-D<option>=<value>`):

| Option | Default | Description |
|--------|---------|-------------|
| `bundle` | `enabled` | Build Arrow, Avro, and Parquet integrations |
| `rest` | `enabled` | Build REST catalog client |
| `hive` | `disabled` | Build Hive (HMS) catalog client |
| `bundle_thrift` | `true` | Reuse Arrow's Thrift dependency for Hive |
| `sql_catalog` | `disabled` | Build SQL catalog client |
| `sql_sqlite` | `disabled` | Build the SQLite connector |
| `sql_postgresql` | `disabled` | Build the PostgreSQL connector |
| `sql_mysql` | `disabled` | Build the MySQL connector |
| `s3` | `disabled` | Build S3 FileIO support |
| `bundle_awssdk` | `true` | Bundle AWS SDK in the Arrow source build for S3 support |
| `rest_integration_test` | `disabled` | Build integration test for REST catalog |
| `spdlog` | `enabled` | Use spdlog as the default logging backend |
| `tests` | `enabled` | Build tests |
Expand Down
93 changes: 93 additions & 0 deletions ci/scripts/test_meson_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

"""Build and run a separate consumer against a Meson installation."""

import argparse
import json
import os
import subprocess
import tempfile
from pathlib import Path


def run(*args, **kwargs):
subprocess.run(args, check=True, **kwargs)


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("builddir", type=Path)
parser.add_argument("--bundle", choices=["enabled", "disabled"], default="enabled")
args = parser.parse_args()
options = {
item["name"]: item["value"]
for item in json.loads(
subprocess.check_output(
["meson", "introspect", str(args.builddir), "--buildoptions"], text=True
)
)
}
prefix = Path(options["prefix"])
run("meson", "install", "-C", str(args.builddir), "--no-rebuild")
pkgdir = prefix / options["libdir"] / "pkgconfig"
bundle_pc = pkgdir / "iceberg_bundle.pc"
if args.bundle == "disabled":
if bundle_pc.exists():
raise RuntimeError("The disabled bundle must not be installed")
elif not bundle_pc.is_file():
raise RuntimeError("The bundle pkg-config file was not installed")
env = os.environ.copy()
additions = {
"PKG_CONFIG_PATH": [str(pkgdir)],
"LD_LIBRARY_PATH": [str(prefix / options["libdir"])],
"DYLD_LIBRARY_PATH": [str(prefix / options["libdir"])],
"PATH": [str(prefix / options["bindir"]), str(prefix / options["libdir"])],
}
for key, paths in additions.items():
env[key] = os.pathsep.join(paths + ([env[key]] if env.get(key) else []))
env["PKG_CONFIG_PATH"] = os.pathsep.join(
[env["PKG_CONFIG_PATH"], *options.get("pkg_config_path", [])]
)
source = Path(__file__).resolve().parents[1] / "test-install"
libraries = [options["default_library"]]
if libraries == ["both"]:
libraries = ["shared", "static"]
for library in libraries:
with tempfile.TemporaryDirectory(prefix="iceberg-consumer-") as tmp:
command = [
"meson",
"setup",
tmp,
str(source),
"--default-library=" + library,
"--buildtype=" + options["buildtype"],
"-Dbundle=" + args.bundle,
]
for component in ("rest", "hive", "sql_catalog"):
command.append("-D" + component + "=" + options.get(component, "disabled"))
for connector in ("sqlite", "postgresql", "mysql"):
option = "sql_" + connector
command.append("-D" + option + "=" + str(options.get(option) == "enabled").lower())
if os.name == "nt":
command.append("--vsenv")
run(*command, env=env)
run("meson", "test", "-C", tmp, "--print-errorlogs", env=env)


if __name__ == "__main__":
main()
124 changes: 124 additions & 0 deletions ci/test-install/bundle_smoke.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

#include <cstdint>
#include <iostream>
#include <memory>

// Verify that the installed format dependency interfaces are usable by consumers.
#include <arrow/api.h>
#include <avro/Compiler.hh>
#include <avro/GenericDatum.hh>
#include <parquet/api/reader.h>

#include "iceberg/arrow/arrow_io_util.h"
#include "iceberg/arrow/arrow_register.h"
#include "iceberg/avro/avro_register.h"
#include "iceberg/file_reader.h"
#include "iceberg/file_writer.h"
#include "iceberg/parquet/parquet_register.h"
#include "iceberg/schema.h"
#include "iceberg/schema_field.h"
#include "iceberg/type.h"

// The borrowed buffers below remain alive until the writer is closed.
void ReleaseBorrowedArray(ArrowArray* array) {
for (int64_t i = 0; i < array->n_children; ++i) {
if (array->children[i]->release) {
array->children[i]->release(array->children[i]);
}
}
array->release = nullptr;
}

iceberg::Status CheckBundle() {
auto avro_schema = avro::compileJsonSchemaFromString(R"("int")");
if (avro::GenericDatum(avro_schema).type() != avro::AVRO_INT) {
return iceberg::InvalidArgument("Avro schema type did not match");
}

iceberg::arrow::RegisterAll();
iceberg::avro::RegisterAll();
iceberg::parquet::RegisterAll();
std::shared_ptr<iceberg::FileIO> io = iceberg::arrow::MakeMockFileIO();
auto schema = std::make_shared<iceberg::Schema>(std::vector<iceberg::SchemaField>{
iceberg::SchemaField::MakeRequired(1, "id", iceberg::int32())});
for (auto format :
{iceberg::FileFormatType::kAvro, iceberg::FileFormatType::kParquet}) {
std::string path = "/roundtrip." + std::string(iceberg::ToString(format));
const int32_t values[] = {42};
const void* value_buffers[] = {nullptr, values};
ArrowArray child{.length = 1,
.n_buffers = 2,
.buffers = value_buffers,
.release = ReleaseBorrowedArray};
ArrowArray* children[] = {&child};
const void* struct_buffers[] = {nullptr};
ArrowArray batch{.length = 1,
.n_buffers = 1,
.n_children = 1,
.buffers = struct_buffers,
.children = children,
.release = ReleaseBorrowedArray};
auto writer = iceberg::WriterFactoryRegistry::Open(
format, {.path = path,
.schema = schema,
.io = io,
.properties = iceberg::WriterProperties::FromMap(
{{"write.parquet.compression-codec", "uncompressed"}})});
if (!writer) return std::unexpected(writer.error());
auto written = (*writer)->Write(&batch);
if (!written) return written;
auto closed = (*writer)->Close();
if (!closed) return closed;
for (bool skip_datum : {true, false}) {
iceberg::ReaderProperties properties;
properties.Set(iceberg::ReaderProperties::kAvroSkipDatum, skip_datum);
auto reader = iceberg::ReaderFactoryRegistry::Open(
format,
{.path = path, .io = io, .projection = schema, .properties = properties});
if (!reader) return std::unexpected(reader.error());
auto next = (*reader)->Next();
if (!next) return std::unexpected(next.error());
if (!next->has_value()) return iceberg::InvalidArgument("Expected one row");
auto& output = next->value();
bool valid = output.length == 1 && output.n_children == 1;
if (valid) {
const auto* column = output.children[0];
valid = column->n_buffers == 2 && column->buffers[1] &&
static_cast<const int32_t*>(column->buffers[1])[column->offset] == 42;
}
output.release(&output);
if (!valid) return iceberg::InvalidArgument("Round-trip value did not match");
closed = (*reader)->Close();
if (!closed) return closed;
if (format == iceberg::FileFormatType::kParquet) break;
}
}
return {};
}

int main() {
auto result = CheckBundle();
if (!result) {
std::cerr << result.error().message << '\n';
return 1;
}
return 0;
}
Loading
Loading