Skip to content

Commit 6c46eea

Browse files
committed
feat(meson): align library modules with CMake
Provide the same library modules through Meson and CMake, including the bundle, Hive, and SQL catalogs, so applications can use either build system. Keep installed static and shared libraries usable by downstream consumers and cover their public interfaces with installation checks. Closes #256 AI assistance was used in this change.
1 parent dcf92f5 commit 6c46eea

33 files changed

Lines changed: 1862 additions & 125 deletions

.github/workflows/test.yml

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ jobs:
208208
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
209209
name: Meson - ${{ matrix.title }}
210210
runs-on: ${{ matrix.runs-on }}
211-
timeout-minutes: 30
211+
timeout-minutes: 60
212212
strategy:
213213
max-parallel: 15
214214
fail-fast: false
@@ -221,9 +221,22 @@ jobs:
221221
meson-setup-args: -Drest_integration_test=enabled
222222
- title: AMD64 Windows 2025
223223
runs-on: windows-2025
224-
meson-setup-args: --vsenv
224+
meson-setup-args: --vsenv --buildtype=release --cmake-prefix-path=C:/vcpkg/installed/x64-windows-static-md
225225
- title: AArch64 macOS 26
226226
runs-on: macos-26
227+
- title: AMD64 Ubuntu 26.04, all catalogs and library variants
228+
runs-on: ubuntu-26.04
229+
CC: gcc-14
230+
CXX: g++-14
231+
sql-clients: true
232+
benchmarks: true
233+
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
234+
- title: AMD64 Ubuntu 26.04, bundle disabled
235+
runs-on: ubuntu-26.04
236+
CC: gcc-14
237+
CXX: g++-14
238+
bundle: disabled
239+
meson-setup-args: -Dbundle=disabled
227240
steps:
228241
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
229242
with:
@@ -244,13 +257,32 @@ jobs:
244257
run: |
245258
echo "CC=sccache ${{ matrix.CC }}" >> $GITHUB_ENV
246259
echo "CXX=sccache ${{ matrix.CXX }}" >> $GITHUB_ENV
260+
- name: Install SQL client development packages
261+
if: ${{ matrix.sql-clients }}
262+
run: |
263+
# Match the PostgreSQL packages preinstalled from PGDG on the runner.
264+
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
265+
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
266+
echo "LIBRARY_PATH=$(pg_config --pkglibdir)" >> "$GITHUB_ENV"
267+
# Complete the system packages' static link interfaces.
268+
sudo sed -i '/^Requires.private:/s/$/ libsasl2/' "$(pkg-config --variable=pcfiledir libcurl)/libcurl.pc"
269+
sudo sed -i '/^Libs.private:/s/$/ -ldb -lsqlite3 -lpq -lmysqlclient/' "$(pkg-config --variable=pcfiledir libsasl2)/libsasl2.pc"
270+
sudo sed -i 's/-lpgcommon /-lpgcommon_shlib /' "$(pkg-config --variable=pcfiledir libpq)/libpq.pc"
271+
- name: Install OpenSSL static dependencies
272+
if: runner.os == 'Linux'
273+
run: sudo apt-get update && sudo apt-get install -y libjitterentropy3-dev
274+
- name: Install Zlib and pkgconf
275+
if: runner.os == 'Windows'
276+
run: |
277+
vcpkg install zlib:x64-windows-static-md pkgconf:x64-windows-static-md
278+
"PKG_CONFIG=C:/vcpkg/installed/x64-windows-static-md/tools/pkgconf/pkgconf.exe" >> $env:GITHUB_ENV
247279
- name: Set up sccache
248280
uses: ./.github/actions/setup-sccache
249281
with:
250282
key-prefix: sccache-meson-${{ matrix.runs-on }}
251283
- name: Build Iceberg
252284
run: |
253-
meson setup builddir ${{ matrix.meson-setup-args || '' }}
285+
meson setup builddir ${{ matrix.meson-setup-args || '' }} --prefix="${{ github.workspace }}/install" --libdir=lib
254286
meson compile -C builddir
255287
- name: Save sccache
256288
if: always()
@@ -261,3 +293,8 @@ jobs:
261293
- name: Test Iceberg
262294
run: |
263295
meson test -C builddir --timeout-multiplier 0 --print-errorlogs
296+
- name: Test installed Meson libraries
297+
run: python ci/scripts/test_meson_install.py builddir --bundle=${{ matrix.bundle || 'enabled' }}
298+
- name: Test Meson benchmarks
299+
if: ${{ matrix.benchmarks }}
300+
run: meson test -C builddir --benchmark --print-errorlogs

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ C++ implementation of [Apache Iceberg™](https://iceberg.apache.org/).
3232
**Required:**
3333

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

3838
**Optional:**
@@ -113,6 +113,24 @@ meson compile -C builddir
113113
meson test -C builddir --timeout-multiplier 0
114114
```
115115

116+
Meson builds `iceberg_bundle` by default, including Arrow filesystem, Avro,
117+
and Parquet support. Use `-Dbundle=disabled` to omit it.
118+
119+
System dependencies are preferred. Arrow, Avro, CRoaring, and sqlpp23 source
120+
fallbacks use upstream CMake; use `--force-fallback-for=arrow,avro` to force
121+
Arrow and Avro source builds. S3 requires `-Ds3=enabled` and either a system
122+
Arrow with S3 support or an installed AWS SDK for the Arrow source build.
123+
124+
Static libraries are the default. Installed libraries are available through
125+
pkg-config, for example `dependency('iceberg_bundle')`. With
126+
`--default-library=both`, use `dependency('iceberg_bundle_static', static: true)`
127+
or `dependency('iceberg_bundle_shared', static: false)` to select a variant;
128+
the same suffixes and `static` argument apply to other modules.
129+
130+
Enable Hive with `-Dhive=enabled`. It reuses Thrift from the Arrow source fallback
131+
by default; use `-Dbundle_thrift=false` for system Thrift. SQL connectors require
132+
their native client development packages.
133+
116134
Meson provides built-in equivalents for several CMake options:
117135

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

124142
| Option | Default | Description |
125143
|--------|---------|-------------|
144+
| `bundle` | `enabled` | Build Arrow, Avro, and Parquet integrations |
126145
| `rest` | `enabled` | Build REST catalog client |
146+
| `hive` | `disabled` | Build Hive (HMS) catalog client |
147+
| `bundle_thrift` | `true` | Reuse Arrow's Thrift dependency for Hive |
148+
| `sql_catalog` | `disabled` | Build SQL catalog client |
149+
| `sql_sqlite` | `disabled` | Build the SQLite connector |
150+
| `sql_postgresql` | `disabled` | Build the PostgreSQL connector |
151+
| `sql_mysql` | `disabled` | Build the MySQL connector |
152+
| `s3` | `disabled` | Build S3 FileIO support |
127153
| `rest_integration_test` | `disabled` | Build integration test for REST catalog |
128154
| `spdlog` | `enabled` | Use spdlog as the default logging backend |
129155
| `tests` | `enabled` | Build tests |

ci/scripts/test_meson_install.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
"""Build and run a separate consumer against a Meson installation."""
19+
20+
import argparse
21+
import json
22+
import os
23+
import subprocess
24+
import tempfile
25+
from pathlib import Path
26+
27+
28+
def run(*args, **kwargs):
29+
subprocess.run(args, check=True, **kwargs)
30+
31+
32+
def main():
33+
parser = argparse.ArgumentParser(description=__doc__)
34+
parser.add_argument("builddir", type=Path)
35+
parser.add_argument("--bundle", choices=["enabled", "disabled"], default="enabled")
36+
args = parser.parse_args()
37+
options = {
38+
item["name"]: item["value"]
39+
for item in json.loads(
40+
subprocess.check_output(
41+
["meson", "introspect", str(args.builddir), "--buildoptions"], text=True
42+
)
43+
)
44+
}
45+
prefix = Path(options["prefix"])
46+
run("meson", "install", "-C", str(args.builddir), "--no-rebuild")
47+
pkgdir = prefix / options["libdir"] / "pkgconfig"
48+
bundle_pc = pkgdir / "iceberg_bundle.pc"
49+
if args.bundle == "disabled":
50+
if bundle_pc.exists():
51+
raise RuntimeError("The disabled bundle must not be installed")
52+
elif not bundle_pc.is_file():
53+
raise RuntimeError("The bundle pkg-config file was not installed")
54+
env = os.environ.copy()
55+
additions = {
56+
"PKG_CONFIG_PATH": [str(pkgdir)],
57+
"LD_LIBRARY_PATH": [str(prefix / options["libdir"])],
58+
"DYLD_LIBRARY_PATH": [str(prefix / options["libdir"])],
59+
"PATH": [str(prefix / options["bindir"]), str(prefix / options["libdir"])],
60+
}
61+
for key, paths in additions.items():
62+
env[key] = os.pathsep.join(paths + ([env[key]] if env.get(key) else []))
63+
env["PKG_CONFIG_PATH"] = os.pathsep.join(
64+
[env["PKG_CONFIG_PATH"], *options.get("pkg_config_path", [])]
65+
)
66+
source = Path(__file__).resolve().parents[1] / "test-install"
67+
libraries = [options["default_library"]]
68+
if libraries == ["both"]:
69+
libraries = ["shared", "static"]
70+
for library in libraries:
71+
with tempfile.TemporaryDirectory(prefix="iceberg-consumer-") as tmp:
72+
command = [
73+
"meson",
74+
"setup",
75+
tmp,
76+
str(source),
77+
"--default-library=" + library,
78+
"--buildtype=" + options["buildtype"],
79+
"-Dbundle=" + args.bundle,
80+
]
81+
for component in ("rest", "hive", "sql_catalog"):
82+
command.append("-D" + component + "=" + options.get(component, "disabled"))
83+
for connector in ("sqlite", "postgresql", "mysql"):
84+
option = "sql_" + connector
85+
command.append("-D" + option + "=" + str(options.get(option) == "enabled").lower())
86+
if os.name == "nt":
87+
command.append("--vsenv")
88+
run(*command, env=env)
89+
run("meson", "test", "-C", tmp, "--print-errorlogs", env=env)
90+
91+
92+
if __name__ == "__main__":
93+
main()

ci/test-install/bundle_smoke.cc

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include <cstdint>
21+
#include <iostream>
22+
#include <memory>
23+
24+
// Verify that the installed format dependency interfaces are usable by consumers.
25+
#include <arrow/api.h>
26+
#include <avro/Compiler.hh>
27+
#include <avro/GenericDatum.hh>
28+
#include <parquet/api/reader.h>
29+
30+
#include "iceberg/arrow/arrow_io_util.h"
31+
#include "iceberg/arrow/arrow_register.h"
32+
#include "iceberg/avro/avro_register.h"
33+
#include "iceberg/file_reader.h"
34+
#include "iceberg/file_writer.h"
35+
#include "iceberg/parquet/parquet_register.h"
36+
#include "iceberg/schema.h"
37+
#include "iceberg/schema_field.h"
38+
#include "iceberg/type.h"
39+
40+
// The borrowed buffers below remain alive until the writer is closed.
41+
void ReleaseBorrowedArray(ArrowArray* array) {
42+
for (int64_t i = 0; i < array->n_children; ++i) {
43+
if (array->children[i]->release) {
44+
array->children[i]->release(array->children[i]);
45+
}
46+
}
47+
array->release = nullptr;
48+
}
49+
50+
iceberg::Status CheckBundle() {
51+
auto avro_schema = avro::compileJsonSchemaFromString(R"("int")");
52+
if (avro::GenericDatum(avro_schema).type() != avro::AVRO_INT) {
53+
return iceberg::InvalidArgument("Avro schema type did not match");
54+
}
55+
56+
iceberg::arrow::RegisterAll();
57+
iceberg::avro::RegisterAll();
58+
iceberg::parquet::RegisterAll();
59+
std::shared_ptr<iceberg::FileIO> io = iceberg::arrow::MakeMockFileIO();
60+
auto schema = std::make_shared<iceberg::Schema>(std::vector<iceberg::SchemaField>{
61+
iceberg::SchemaField::MakeRequired(1, "id", iceberg::int32())});
62+
for (auto format :
63+
{iceberg::FileFormatType::kAvro, iceberg::FileFormatType::kParquet}) {
64+
std::string path = "/roundtrip." + std::string(iceberg::ToString(format));
65+
const int32_t values[] = {42};
66+
const void* value_buffers[] = {nullptr, values};
67+
ArrowArray child{.length = 1,
68+
.n_buffers = 2,
69+
.buffers = value_buffers,
70+
.release = ReleaseBorrowedArray};
71+
ArrowArray* children[] = {&child};
72+
const void* struct_buffers[] = {nullptr};
73+
ArrowArray batch{.length = 1,
74+
.n_buffers = 1,
75+
.n_children = 1,
76+
.buffers = struct_buffers,
77+
.children = children,
78+
.release = ReleaseBorrowedArray};
79+
auto writer = iceberg::WriterFactoryRegistry::Open(
80+
format, {.path = path,
81+
.schema = schema,
82+
.io = io,
83+
.properties = iceberg::WriterProperties::FromMap(
84+
{{"write.parquet.compression-codec", "uncompressed"}})});
85+
if (!writer) return std::unexpected(writer.error());
86+
auto written = (*writer)->Write(&batch);
87+
if (!written) return written;
88+
auto closed = (*writer)->Close();
89+
if (!closed) return closed;
90+
for (bool skip_datum : {true, false}) {
91+
iceberg::ReaderProperties properties;
92+
properties.Set(iceberg::ReaderProperties::kAvroSkipDatum, skip_datum);
93+
auto reader = iceberg::ReaderFactoryRegistry::Open(
94+
format,
95+
{.path = path, .io = io, .projection = schema, .properties = properties});
96+
if (!reader) return std::unexpected(reader.error());
97+
auto next = (*reader)->Next();
98+
if (!next) return std::unexpected(next.error());
99+
if (!next->has_value()) return iceberg::InvalidArgument("Expected one row");
100+
auto& output = next->value();
101+
bool valid = output.length == 1 && output.n_children == 1;
102+
if (valid) {
103+
const auto* column = output.children[0];
104+
valid = column->n_buffers == 2 && column->buffers[1] &&
105+
static_cast<const int32_t*>(column->buffers[1])[column->offset] == 42;
106+
}
107+
output.release(&output);
108+
if (!valid) return iceberg::InvalidArgument("Round-trip value did not match");
109+
closed = (*reader)->Close();
110+
if (!closed) return closed;
111+
if (format == iceberg::FileFormatType::kParquet) break;
112+
}
113+
}
114+
return {};
115+
}
116+
117+
int main() {
118+
auto result = CheckBundle();
119+
if (!result) {
120+
std::cerr << result.error().message << '\n';
121+
return 1;
122+
}
123+
return 0;
124+
}

0 commit comments

Comments
 (0)