Skip to content

Commit

Permalink
feat: Add Zig Bindings Module (#2374)
Browse files Browse the repository at this point in the history
* zig binding

* more changes:
- devcontainer: add zigup (like: rustup unofficial)
- CI: zig binding
- zig build: build opendal_c library

* rm unecessary info
  • Loading branch information
kassane authored May 30, 2023
1 parent fc7207e commit 59af646
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .devcontainer/post_create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
63 changes: 63 additions & 0 deletions .github/workflows/bindings_zig.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions bindings/zig/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
zig-cache/
zig-out/
53 changes: 53 additions & 0 deletions bindings/zig/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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
```

17 changes: 17 additions & 0 deletions bindings/zig/README.md
Original file line number Diff line number Diff line change
@@ -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)
48 changes: 48 additions & 0 deletions bindings/zig/build.zig
Original file line number Diff line number Diff line change
@@ -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",
});
}
74 changes: 74 additions & 0 deletions bindings/zig/src/opendal.zig
Original file line number Diff line number Diff line change
@@ -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]);
}
}

0 comments on commit 59af646

Please sign in to comment.