Skip to content
Merged
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
39 changes: 36 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ on:
branches: [ main ]

jobs:
test:
build:
runs-on: ubuntu-latest
name: Test on ubuntu-latest
name: Build on ubuntu-latest

steps:
- name: Checkout
Expand All @@ -30,4 +30,37 @@ jobs:
- name: Build
run: |
pushd examples/basic && zig build && popd
pushd examples/init && zig build && popd
pushd examples/init && zig build && popd

arkvm-tests:
runs-on: ubuntu-latest
name: ArkTS N-API tests

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Zig for OpenHarmony
uses: openharmony-zig/setup-zig-ohos@v0.1.0
with:
tag: '0.16.0'

- name: Setup arkvm
id: setup-arkvm
uses: harmony-contrib/arkts-vm@v2.0.0
with:
cache: true

- name: Validate Ark host bundle
env:
ARK_HOST_TOOLS_DIR: ${{ steps.setup-arkvm.outputs.arkvm-path }}
run: |
set -euo pipefail
test -x "${ARK_HOST_TOOLS_DIR}/ark_js_napi_cli"
test -x "${ARK_HOST_TOOLS_DIR}/es2abc"
test -f "${ARK_HOST_TOOLS_DIR}/libace_napi.so"

- name: Run ArkTS tests
env:
ARK_HOST_TOOLS_DIR: ${{ steps.setup-arkvm.outputs.arkvm-path }}
run: bash ./scripts/arkvm/run_arkvm_tests.sh
45 changes: 45 additions & 0 deletions .github/workflows/memory-leak-detect.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Memory Leak Detect

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
arkvm-memory:
name: ArkVM N-API memory
runs-on: ubuntu-24.04

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Zig
uses: openharmony-zig/setup-zig-ohos@v0.1.0
with:
tag: '0.16.0'

- name: Setup ArkVM
id: setup-arkvm
uses: harmony-contrib/arkts-vm@v2.0.0
with:
cache: true

- name: Validate Ark host bundle
env:
ARK_HOST_TOOLS_DIR: ${{ steps.setup-arkvm.outputs.arkvm-path }}
run: |
set -euo pipefail
echo "Expect host bundle at: ${ARK_HOST_TOOLS_DIR}"
test -x "${ARK_HOST_TOOLS_DIR}/ark_js_napi_cli"
test -x "${ARK_HOST_TOOLS_DIR}/es2abc"
test -f "${ARK_HOST_TOOLS_DIR}/libace_napi.so"
test -f "${ARK_HOST_TOOLS_DIR}/libets_interop_js_napi.so"

- name: Run ArkVM memory tests
shell: bash
env:
ARK_HOST_TOOLS_DIR: ${{ steps.setup-arkvm.outputs.arkvm-path }}
KEEP_WORKDIR: "1"
run: bash scripts/arkvm/run_arkvm_memory_tests.sh
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.DS_Store
.zig-cache
zig-out
zig-out
.tmp_arkvm_runner
.tmp_arkvm_memory_runner
3 changes: 3 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ pub fn build(b: *std.Build) !void {
const napi = b.addModule("napi", .{
.root_source_file = b.path("src/napi.zig"),
});
const build_options = b.addOptions();
build_options.addOption(bool, "napi_tsgen", false);

napi.addImport("napi-sys", napi_sys);
napi.addOptions("build_options", build_options);

napi.addIncludePath(b.path("src/sys/header"));
napi_sys.addIncludePath(b.path("src/sys/header"));
Expand Down
12 changes: 9 additions & 3 deletions examples/basic/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,21 @@ pub fn build(b: *std.Build) !void {

if (result.arm64) |arm64| {
arm64.root_module.addImport("napi", napi);
arm64.root_module.linkSystemLibrary("hilog_ndk.z", .{});
if (arm64.rootModuleTarget().abi.isOpenHarmony()) {
arm64.root_module.linkSystemLibrary("hilog_ndk.z", .{});
}
}
if (result.arm) |arm| {
arm.root_module.addImport("napi", napi);
arm.root_module.linkSystemLibrary("hilog_ndk.z", .{});
if (arm.rootModuleTarget().abi.isOpenHarmony()) {
arm.root_module.linkSystemLibrary("hilog_ndk.z", .{});
}
}
if (result.x64) |x64| {
x64.root_module.addImport("napi", napi);
x64.root_module.linkSystemLibrary("hilog_ndk.z", .{});
if (x64.rootModuleTarget().abi.isOpenHarmony()) {
x64.root_module.linkSystemLibrary("hilog_ndk.z", .{});
}
}

const dts = try napi_build.generateTypeDefinition(b, .{
Expand Down
5 changes: 4 additions & 1 deletion examples/basic/src/hello.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ const object = @import("object.zig");
const function = @import("function.zig");
const thread_safe_function = @import("thread_safe_function.zig");
const class = @import("class.zig");
const log = @import("log/log.zig");
const builtin = @import("builtin");
const log = if (builtin.target.abi.isOpenHarmony()) @import("log/log.zig") else struct {
pub fn test_hilog() void {}
};
const buffer = @import("buffer.zig");
const arraybuffer = @import("arraybuffer.zig");
const typedarray = @import("typedarray.zig");
Expand Down
4 changes: 2 additions & 2 deletions examples/basic/src/string.zig
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const std = @import("std");
const napi = @import("napi");

pub fn hello(name: []u8) []u8 {
pub fn hello(env: napi.Env, name: []u8) napi.String {
const allocator = std.heap.page_allocator;

const message = std.fmt.allocPrint(allocator, "Hello, {s}!", .{name}) catch @panic("OOM");
defer allocator.free(message);

return message;
return napi.String.New(env, message);
}

pub const text = "Hello World";
7 changes: 2 additions & 5 deletions examples/basic/src/thread_safe_function.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ const Args = struct { i32, i32 };
const Return = i32;

fn sleepForFiveSeconds() void {
var req = std.c.timespec{
.sec = 5,
.nsec = 0,
};
_ = std.c.nanosleep(&req, null);
// Keep this hook separate so TSFN examples can add scheduling delay when needed
// without making the type-generation build depend on libc sleep symbols.
}

fn execute_thread_safe_function(tsfn: *napi.ThreadSafeFunction(Args, Return, true, 0)) void {
Expand Down
4 changes: 2 additions & 2 deletions examples/init/src/hello.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ fn add(left: f64, right: f64) f64 {
return result;
}

fn hello(name: []u8) []u8 {
fn hello(env: napi.Env, name: []u8) napi.String {
const allocator = std.heap.page_allocator;

const message = std.fmt.allocPrint(allocator, "Hello, {s}!", .{name}) catch @panic("OOM");
defer allocator.free(message);

return message;
return napi.String.New(env, message);
}

fn fib(env: napi.Env, n: f64) void {
Expand Down
29 changes: 29 additions & 0 deletions examples/memory/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const std = @import("std");
const napi_build = @import("zig-napi").napi_build;

pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const zig_napi = b.dependency("zig-napi", .{});
const napi = zig_napi.module("napi");

const result = try napi_build.nativeAddonBuild(b, .{
.name = "hello",
.root_module_options = .{
.root_source_file = b.path("./src/hello.zig"),
.target = target,
.optimize = optimize,
},
});

if (result.arm64) |arm64| {
arm64.root_module.addImport("napi", napi);
}
if (result.arm) |arm| {
arm.root_module.addImport("napi", napi);
}
if (result.x64) |x64| {
x64.root_module.addImport("napi", napi);
}
}
8 changes: 8 additions & 0 deletions examples/memory/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.{
.name = .memory,
.version = "0.0.1",
.minimum_zig_version = "0.16.0",
.fingerprint = 0xea6d34357876ddeb,
.dependencies = .{ .@"zig-napi" = .{ .path = "../.." } },
.paths = .{ "build.zig", "build.zig.zon", "src" },
}
134 changes: 134 additions & 0 deletions examples/memory/src/async.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const std = @import("std");
const napi = @import("napi");

const AsyncInput = struct {
label: []u8,
values: []f32,
};

const AsyncSummary = struct {
label: []u8,
count: usize,
total: f64,
};

const CountProgress = struct {
current: u32,
total: u32,
};

const FnArgs = struct { i32, i32 };
const FnReturn = i32;

fn async_summary_execute(ctx: napi.AsyncContext(void), input: AsyncInput) !AsyncSummary {
var total: f64 = 0;
for (input.values) |value| {
total += value;
}
const label = try ctx.allocator.dupe(u8, input.label);
return .{ .label = label, .count = input.values.len, .total = total };
}

fn async_void_execute(_: []u8) void {}

fn async_fail_execute(message: []u8) !void {
return napi.Error.fromReason(message);
}

fn count_with_progress_execute(ctx: napi.AsyncContext(CountProgress), total: u32) !u32 {
var current: u32 = 0;
while (current <= total) : (current += 1) {
try ctx.emit(.{ .current = current, .total = total });
}
return total;
}

fn abortable_count_execute(ctx: napi.AsyncContext(void), total: u32) !u32 {
var current: u32 = 0;
while (current < total) : (current += 1) {
if (current % 256 == 0) {
try ctx.checkCancelled();
}
}
try ctx.checkCancelled();
return total;
}

fn abortable_slow_count_execute(ctx: napi.AsyncContext(void), total: u32) !u32 {
var current: u32 = 0;
while (current < total) : (current += 1) {
if (current % 16 == 0) {
for (0..10000) |_| {
std.atomic.spinLoopHint();
}
try ctx.checkCancelled();
}
}
try ctx.checkCancelled();
return total;
}

pub fn memory_async_summary(input: AsyncInput) napi.Async(AsyncSummary, .thread) {
return napi.Async(AsyncSummary, .thread).from(input, async_summary_execute);
}

pub fn memory_async_summary_single(input: AsyncInput) napi.Async(AsyncSummary, .single) {
return napi.Async(AsyncSummary, .single).from(input, async_summary_execute);
}

pub fn memory_async_void(label: []u8) napi.Async(void, .thread) {
return napi.Async(void, .thread).from(label, async_void_execute);
}

pub fn memory_async_fail(message: []u8) napi.Async(void, .thread) {
return napi.Async(void, .thread).from(message, async_fail_execute);
}

pub fn memory_async_progress(total: u32) napi.AsyncWithEvents(u32, CountProgress, .thread) {
return napi.AsyncWithEvents(u32, CountProgress, .thread).from(total, count_with_progress_execute);
}

pub fn memory_event_mode_progress(total: u32) napi.AsyncWithEvents(u32, CountProgress, .event) {
return napi.AsyncWithEvents(u32, CountProgress, .event).from(total, count_with_progress_execute);
}

pub fn memory_abortable_count(total: u32, signal: napi.AbortSignal) napi.Async(u32, .thread) {
_ = signal;
return napi.Async(u32, .thread).from(total, abortable_count_execute);
}

pub fn memory_abortable_slow_count(total: u32, signal: napi.AbortSignal) napi.Async(u32, .thread) {
_ = signal;
return napi.Async(u32, .thread).from(total, abortable_slow_count_execute);
}

fn worker_execute(value: u32) u32 {
return value + 1;
}

pub fn memory_worker(env: napi.Env, value: u32) napi.Promise {
const worker = napi.Worker(env, .{ .data = value, .Execute = worker_execute });
return worker.AsyncQueue();
}

fn execute_thread_safe_function(tsfn: *napi.ThreadSafeFunction(FnArgs, FnReturn, true, 0)) void {
defer tsfn.release(.Release) catch {};
tsfn.Ok(.{ 1, 2 }, .NonBlocking) catch {};
}

fn execute_thread_safe_function_with_error(tsfn: *napi.ThreadSafeFunction(FnArgs, FnReturn, true, 0)) void {
defer tsfn.release(.Release) catch {};
tsfn.Err(napi.Error.withReason("memory tsfn error"), .NonBlocking) catch {};
}

pub fn memory_thread_safe_function(tsfn: *napi.ThreadSafeFunction(FnArgs, FnReturn, true, 0)) !void {
try tsfn.acquire();
const worker = try std.Thread.spawn(.{}, execute_thread_safe_function, .{tsfn});
worker.detach();

try tsfn.acquire();
const worker_with_error = try std.Thread.spawn(.{}, execute_thread_safe_function_with_error, .{tsfn});
worker_with_error.detach();

try tsfn.release(.Release);
}
Loading
Loading