Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Client] Add support for zig client library #160

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions clients/zig/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.zig-cache/*
zig-out/*
20 changes: 20 additions & 0 deletions clients/zig/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@


.PHONY: test coverage run run-ex build-ex


test: test-cac test-expt

test-cac:
zig test -I../../headers -lc src/cac.zig ../../target/debug/libcac_client.so
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NishantJoshi00 few things here:

  • Can the linking binary be an absolute path
  • This should be different based on the OS compiled for

If any support is needed from Superposition, we can add this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer required, I was using this initially to test it. Will remove this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the libs and headers are figured out in build by build.zig


test-expt:
zig test -I../../headers -lc src/expt.zig ../../target/debug/libexperimentation_client.so


run-ex: build-ex
./zig-out/bin/cli

build-ex:
zig build

26 changes: 26 additions & 0 deletions clients/zig/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
### Pauli

A zig based client SDK for [`superposition`](https://github.com/juspay/superposition) service.

### Pre-requisites

- As this client leverages `build.zig.zon` file for managing dependencies. Your zig version should atleast satisfy the minimum version mentioned in the `build.zig.zon` file.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NishantJoshi00 can we include some usage examples too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the example/ folder. I will add a README in that folder too

- You should have `superposition` service running on your local machine or on a remote server. (This is optional in the initial development phase, but might be required for testing)
- The build steps consist of the following steps:
- Pulling the superposition repository, this implies that you have `git` installed on your machine and you have access to the repository.
- This is followed by the build stage of `superposition`. (required to get the shared library) `rust` and `cargo` should be installed on your machine for this stage to complete.

### Get Started

- To get started with the client, create your own zig project:
```bash
mkdir my_project
cd my_project
zig init-exe
```
- To install the client as a dependency run the following command:
```bash
zig fetch --save <url/path>
```
Here, the URL can be the place where the zig client is hosted or the path to the client on your local machine.
The url/path should allow the zig build system to discover the `build.zig.zon` file and the paths included within it.
87 changes: 87 additions & 0 deletions clients/zig/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});

const optimize = b.standardOptimizeOption(.{});

const superposition = buildSuperpositionClient(b);

const lib = b.addStaticLibrary(.{
.name = "pauli",
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});

lib.step.dependOn(&superposition.step);

linkBindingsDebug(b, lib);

b.installArtifact(lib);

// Tests section

const test_step = b.step("test", "Run unit tests");

// Root Tests

const lib_unit_tests = b.addTest(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});

lib_unit_tests.step.dependOn(&superposition.step);

linkBindingsDebug(b, lib_unit_tests);

const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
test_step.dependOn(&run_lib_unit_tests.step);

const module = b.addModule("pauli", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});

module.linkLibrary(lib);

module.addIncludePath(b.path("superposition/headers"));
module.addLibraryPath(b.path("superposition/target/release"));

module.linkSystemLibrary("c", .{});

module.linkSystemLibrary("cac_client", .{});
module.linkSystemLibrary("experimentation_client", .{});
}

fn linkBindingsDebug(b: *std.Build, c: *std.Build.Step.Compile) void {
c.addIncludePath(b.path("superposition/headers"));
c.addLibraryPath(b.path("superposition/target/release"));

c.linkLibC();
c.linkSystemLibrary("cac_client");
c.linkSystemLibrary("experimentation_client");
}

fn buildSuperpositionClient(b: *std.Build) *std.Build.Step.Run {
const repository = b.addSystemCommand(&.{
"sh", "-c", std.fmt.comptimePrint(
\\if [ ! -d superposition ]; then
\\ git clone https://github.com/juspay/superposition.git
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we avoid this clone? Can you link to the libraries in target directory in the parent folder?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think when using this client as a dependency, we need some mechanism to be able to link the library, header files. By, doing this I don't need to worry about if you have the .so files present in any of your lib paths (same goes to header files). But, it does incur the cost of compilation.

I think one possible fix might be to only build the client crates, while not touching the others.

What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you can only build the clients rather than the whole project

\\fi
, .{}),
});

const rust_build = b.addSystemCommand(&.{ "cargo", "build", "--release" });

rust_build.setCwd(b.path("superposition"));

rust_build.step.dependOn(&repository.step);

return rust_build;
}
72 changes: 72 additions & 0 deletions clients/zig/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = "pauli",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NishantJoshi00 , what is pauli here, the package/lib name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a generic name zig-cac-client? We don't want to get into naming clients for other languages too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, I started of with this, will change that


// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",

// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.0",
NishantJoshi00 marked this conversation as resolved.
Show resolved Hide resolved

// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",

// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},

// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package. Only files listed here will remain on disk
// when using the zig package manager. As a rule of thumb, one should list
// files required for compilation plus any license(s).
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
"README.md",
},
}
2 changes: 2 additions & 0 deletions clients/zig/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.zig-cache/*
zig-out/*
75 changes: 75 additions & 0 deletions clients/zig/example/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});

// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});

const package = b.dependency("pauli", .{
.target = target,
.optimize = optimize,
});

const module = package.module("pauli");

const exe = b.addExecutable(.{
.name = "example",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});

exe.root_module.addImport("pauli", module);

// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(exe);

// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);

// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());

// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}

// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);

const exe_unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});

const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);

// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}
39 changes: 39 additions & 0 deletions clients/zig/example/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = "example",

// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",

// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.0",

// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
.pauli = .{
.url = "../",
.hash = "12206eb33ebbd1b0936dcb84d44460da9a863150e60e2432cef3ddf3fb6fd75a2f09",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}
20 changes: 20 additions & 0 deletions clients/zig/example/src/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const cac_client = @import("pauli").cac;
const std = @import("std");

pub fn main() !void {
var logging_alloc = std.heap.loggingAllocator(std.heap.page_allocator);
const alloc = logging_alloc.allocator();

const setup_config = cac_client.SetupConfig{ .tenant = "public", .update_frequency = 10, .hostname = "http://localhost:8080" };
NishantJoshi00 marked this conversation as resolved.
Show resolved Hide resolved

const client = try cac_client.Client.init(setup_config);

std.log.info("Client Initialized", .{});

const input = [_][]const u8{"test"};

const config = struct { test_2: u8 };

const output_1 = try client.getDefaultConfig(config, alloc, &input);
std.log.info("Output: {}", .{output_1});
}
Loading
Loading