diff --git a/.devcontainer/post_create.sh b/.devcontainer/post_create.sh index c9cbdff1735a..3e68e6cf7368 100644 --- a/.devcontainer/post_create.sh +++ b/.devcontainer/post_create.sh @@ -45,3 +45,10 @@ sudo make sudo cp lib/*.a /usr/lib sudo ln -s /usr/lib/libgtest.a /usr/local/lib/libgtest.a sudo ln -s /usr/lib/libgtest_main.a /usr/local/lib/libgtest_main.a + +# Setup for Zig binding +sudo apt install -y wget +wget -q https://github.com/marler8997/zigup/releases/download/v2022_08_25/zigup.ubuntu-latest-x86_64.zip +unzip zigup.ubuntu-latest-x86_64.zip -d /usr/bin +chmod +x /usr/bin/zigup +zigup master #TODO: replace to 0.11.0 (stable) \ No newline at end of file diff --git a/.github/workflows/bindings_zig.yml b/.github/workflows/bindings_zig.yml new file mode 100644 index 000000000000..61c237ea47f1 --- /dev/null +++ b/.github/workflows/bindings_zig.yml @@ -0,0 +1,63 @@ +# 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. + +name: Bindings Zig CI + +on: + push: + branches: + - main + tags: + - '*' + pull_request: + branches: + - main + paths: + - "bindings/zig/**" + - ".github/workflows/bindings_zig.yml" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: goto-bus-stop/setup-zig@v2 + with: + version: master # 0.11.0 (nightly/master) + #version: 0.11.0 # TODO: replace to stable version - wait new zig release + + - name: Setup Rust toolchain + uses: ./.github/actions/setup + + - name: Build Zig binding + working-directory: "bindings/zig" + run: zig build opendal_c + + - name: Check diff + run: git diff --exit-code + + - name: Build and Run BDD tests + working-directory: "bindings/zig" + run: zig build test -fsummary diff --git a/bindings/zig/.gitignore b/bindings/zig/.gitignore new file mode 100644 index 000000000000..4a0641ed7f18 --- /dev/null +++ b/bindings/zig/.gitignore @@ -0,0 +1,2 @@ +zig-cache/ +zig-out/ \ No newline at end of file diff --git a/bindings/zig/CONTRIBUTING.md b/bindings/zig/CONTRIBUTING.md new file mode 100644 index 000000000000..e882c2faafe6 --- /dev/null +++ b/bindings/zig/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing +- [Contributing](#contributing) + - [Setup](#setup) + - [Using a dev container environment](#using-a-dev-container-environment) + - [Bring your own toolbox](#bring-your-own-toolbox) + - [Build](#build) + - [Test](#test) + - [Docs](#docs) + - [Misc](#misc) + +## Setup + +### Using a dev container environment +OpenDAL provides a pre-configured [dev container](https://containers.dev/) that could be used in [Github Codespaces](https://github.com/features/codespaces), [VSCode](https://code.visualstudio.com/), [JetBrains](https://www.jetbrains.com/remote-development/gateway/), [JuptyerLab](https://jupyterlab.readthedocs.io/en/stable/). Please pick up your favourite runtime environment. + +The fastest way is: + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/apache/incubator-opendal?quickstart=1&machine=standardLinux32gb) + +### Bring your own toolbox +To build OpenDAL Zig binding, the following is all you need: +- **Zig toolchain** is based on **LLVM-toolchain**(clang/++, libcxx, lld). + +## Need +- opendal C binding + +## Build +To build the library and header file. +```shell +zig build opendal_c +``` + +- zig build need add header file `opendal.h` is under `../c/include` +- The library is under `../../target/debug` or `../../target/release` after building. + +To clean the build results. +```shell +zig build uninstall +``` + +## Test +To build and run the tests. +```shell +zig build test -fsummary +``` + +```text +Build Summary: 3/3 steps succeeded; 1/1 tests passed +test success +└─ run test 1 passed 960us MaxRSS:3M + └─ zig test Debug native success 982ms MaxRSS:164M +``` + diff --git a/bindings/zig/README.md b/bindings/zig/README.md new file mode 100644 index 000000000000..378e09475d19 --- /dev/null +++ b/bindings/zig/README.md @@ -0,0 +1,17 @@ +# OpenDAL Zig Binding (WIP) + +# Build Zig bindings + +To compile OpenDAL from source code, you'll need: +- [Zig 0.11.0 or higher](https://ziglang.org/download), totally portable. + +```bash +# build opendal_c library (call make -C ../c) +zig build opendal_c +# Build and run test +zig build test -fsummary +``` + +## License + +[Apache v2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/bindings/zig/build.zig b/bindings/zig/build.zig new file mode 100644 index 000000000000..b27f2999f000 --- /dev/null +++ b/bindings/zig/build.zig @@ -0,0 +1,48 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // This function creates a module and adds it to the package's module set, making + // it available to other packages which depend on this one. + _ = b.addModule("opendal", .{ + .source_file = .{ + .path = "src/opendal.zig", + }, + .dependencies = &.{}, + }); + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const unit_tests = b.addTest(.{ + .root_source_file = .{ + .path = "src/opendal.zig", + }, + .target = target, + .optimize = optimize, + }); + unit_tests.addIncludePath("../c/include"); + if (optimize == .Debug) + unit_tests.addLibraryPath("../../target/debug") + else + unit_tests.addLibraryPath("../../target/release"); + unit_tests.linkSystemLibrary("opendal_c"); + unit_tests.linkLibC(); + + const opendal_c = buildOpendalC(b); + const make_opendal_c = b.step("opendal_c", "Build opendal_c library"); + make_opendal_c.dependOn(&opendal_c.step); + const run_unit_tests = b.addRunArtifact(unit_tests); + const test_step = b.step("test", "Run opendal tests"); + test_step.dependOn(&run_unit_tests.step); +} +fn buildOpendalC(b: *std.Build) *std.Build.Step.Run { + const rootdir = (comptime std.fs.path.dirname(@src().file) orelse null) ++ "/"; + const opendalCdir = rootdir ++ "../c"; + return b.addSystemCommand(&[_][]const u8{ + "make", + "-C", + opendalCdir, + "build", + }); +} diff --git a/bindings/zig/src/opendal.zig b/bindings/zig/src/opendal.zig new file mode 100644 index 000000000000..0656efc83968 --- /dev/null +++ b/bindings/zig/src/opendal.zig @@ -0,0 +1,74 @@ +pub const opendal = @cImport(@cInclude("opendal.h")); + +const std = @import("std"); +const testing = std.testing; + +test "Opendal BDD test" { + const c_str = [*:0]const u8; // const char* + // const zig_str = []const u8; + + const OpendalBddTest = struct { + p: opendal.opendal_operator_ptr, + scheme: c_str, + path: c_str, + content: c_str, + + pub fn init() Self { + var self: Self = undefined; + self.scheme = "memory"; + self.path = "test"; + self.content = "Hello, World!"; + + var options: opendal.opendal_operator_options = opendal.opendal_operator_options_new(); + opendal.opendal_operator_options_set(&options, "root", "/myroot"); + + // Given A new OpenDAL Blocking Operator + self.p = opendal.opendal_operator_new(self.scheme, options); + defer opendal.opendal_operator_options_free(&options); + // try testing.expect(self.p != null); + return self; + } + pub fn deinit(self: *Self) void { + opendal.opendal_operator_free(&self.p); + } + + const Self = @This(); + }; + var bddTest = OpendalBddTest.init(); + defer bddTest.deinit(); + + // When Blocking write path "test" with content "Hello, World!" + const data: opendal.opendal_bytes = .{ + .data = bddTest.content, + // c_str hasn't len field (.* is ptr) + .len = std.mem.len(bddTest.content), + }; + const code = opendal.opendal_operator_blocking_write(bddTest.p, bddTest.path, data); + try testing.expectEqual(code, opendal.OPENDAL_OK); + + // The blocking file "test" should exist + var e: opendal.opendal_result_is_exist = opendal.opendal_operator_is_exist(bddTest.p, bddTest.path); + try testing.expectEqual(e.code, opendal.OPENDAL_OK); + try testing.expect(e.is_exist); + + // The blocking file "test" entry mode must be file + var s: opendal.opendal_result_stat = opendal.opendal_operator_stat(bddTest.p, bddTest.path); + try testing.expectEqual(s.code, opendal.OPENDAL_OK); + var meta: opendal.opendal_metadata = s.meta; + try testing.expect(opendal.opendal_metadata_is_file(&meta)); + + // The blocking file "test" content length must be 13 + try testing.expectEqual(opendal.opendal_metadata_content_length(&meta), 13); + defer opendal.opendal_metadata_free(&meta); + + // The blocking file "test" must have content "Hello, World!" + var r: opendal.opendal_result_read = opendal.opendal_operator_blocking_read(bddTest.p, bddTest.path); + defer opendal.opendal_bytes_free(r.data); + try testing.expect(r.code == opendal.OPENDAL_OK); + try testing.expectEqual(std.mem.len(r.data.*.data), std.mem.len(bddTest.content)); + + var count: usize = 0; + while (count < std.mem.len(r.data.*.data)) : (count += 1) { + try testing.expectEqual(bddTest.content[count], r.data.*.data[count]); + } +}