Skip to content
Open
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
4 changes: 2 additions & 2 deletions .changepacks/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ignore": ["**", "!packages/python/pyproject.toml", "!packages/dotnet/BraillifyNet/BraillifyNet.csproj", "!packages/dotnet/Braillify/Braillify.csproj", "!packages/node/package.json", "!libs/braillify/Cargo.toml"],
"ignore": ["**", "!packages/python/pyproject.toml", "!packages/dotnet/BraillifyNet/BraillifyNet.csproj", "!packages/dotnet/Braillify/Braillify.csproj", "!packages/node/package.json", "!packages/c/Cargo.toml", "!libs/braillify/Cargo.toml"],
"baseBranch": "main",
"latestPackage": null,
"publish": {}
}
}
143 changes: 143 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ jobs:
- name: Test (Python)
timeout-minutes: 10
run: bun run test:python
- name: Set up MSVC for C/C++ tests
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
- name: Test (C and C++)
timeout-minutes: 5
run: bun run test:c
- name: Format Rollback
shell: bash
run: |
Expand Down Expand Up @@ -819,3 +825,140 @@ jobs:
name: nuget-packages
path: packages/dotnet/**/nupkg/*.nupkg
retention-days: 1

# c
c-build:
name: C Build - ${{ matrix.rid }}
runs-on: ${{ matrix.runner }}
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/c/Cargo.toml') }}
needs:
- test
- changepacks
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-22.04
target: x86_64-unknown-linux-gnu
rid: linux-x64
archive: braillify-c-linux-x64.tar.gz
- runner: ubuntu-22.04
target: aarch64-unknown-linux-gnu
rid: linux-arm64
archive: braillify-c-linux-arm64.tar.gz
- runner: macos-14
target: x86_64-apple-darwin
rid: macos-x64
archive: braillify-c-macos-x64.tar.gz
- runner: macos-14
target: aarch64-apple-darwin
rid: macos-arm64
archive: braillify-c-macos-arm64.tar.gz
- runner: windows-2022
target: x86_64-pc-windows-msvc
rid: windows-x64
archive: braillify-c-windows-x64.tar.gz
steps:
- uses: actions/checkout@v7

- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}

- name: Set up MSVC
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1

- name: Install cross-compilation tools (Linux ARM64)
if: matrix.rid == 'linux-arm64'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu

- name: Build C libraries
run: cargo build --release --target ${{ matrix.target }} -p braillify-c
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc

- name: Bundle libraries and header
shell: bash
env:
TARGET: ${{ matrix.target }}
RID: ${{ matrix.rid }}
ARCHIVE: ${{ matrix.archive }}
run: |
mkdir -p "dist/$RID/include" "dist/$RID/lib"
cp packages/c/include/braillify.h "dist/$RID/include/"
cp packages/c/README.md "dist/$RID/"
case "$RUNNER_OS" in
Linux)
cp "target/$TARGET/release/libbraillify_c.a" "dist/$RID/lib/"
cp "target/$TARGET/release/libbraillify_c.so" "dist/$RID/lib/"
;;
macOS)
cp "target/$TARGET/release/libbraillify_c.a" "dist/$RID/lib/"
cp "target/$TARGET/release/libbraillify_c.dylib" "dist/$RID/lib/"
;;
Windows)
cp "target/$TARGET/release/braillify_c.dll" "dist/$RID/lib/"
cp "target/$TARGET/release/braillify_c.dll.lib" "dist/$RID/lib/"
cp "target/$TARGET/release/braillify_c.lib" "dist/$RID/lib/"
;;
esac
tar -C dist -czf "$ARCHIVE" "$RID"

- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: c-bindings-${{ matrix.rid }}
path: ${{ matrix.archive }}
if-no-files-found: error
retention-days: 1

c-publish:
name: C Publish
runs-on: ubuntu-latest
if: ${{ contains(needs.changepacks.outputs.changepacks, 'packages/c/Cargo.toml') }}
needs:
- changepacks
- c-build
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Download C artifacts
uses: actions/download-artifact@v8
with:
pattern: c-bindings-*
path: c-artifacts
merge-multiple: true

- name: Generate artifact attestation
uses: actions/attest-build-provenance@v4
with:
subject-path: "c-artifacts/*.tar.gz"

- name: Delete existing release assets (idempotent re-run)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPLOAD_URL: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['packages/c/Cargo.toml'] }}
run: |
RELEASE_ID=$(echo "$UPLOAD_URL" | sed -E 's#.*/releases/([0-9]+)/assets.*#\1#')
REPO="${GITHUB_REPOSITORY}"
for ARCHIVE in c-artifacts/*.tar.gz; do
NAME=$(basename "$ARCHIVE")
ASSET_ID=$(gh api "repos/$REPO/releases/$RELEASE_ID/assets" \
--paginate --jq ".[] | select(.name == \"$NAME\") | .id" || true)
if [ -n "$ASSET_ID" ]; then
gh api -X DELETE "repos/$REPO/releases/assets/$ASSET_ID" || true
fi
done

- name: Upload release assets
uses: owjs3901/upload-github-release-asset@main
with:
upload_url: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['packages/c/Cargo.toml'] }}
asset_path: "c-artifacts/*.tar.gz"
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
"scripts": {
"lint": "oxlint . && cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings",
"lint:fix": "oxlint . --fix && cargo fmt --all && cargo clippy --workspace --all-targets --fix --allow-dirty --allow-staged -- -D warnings && cargo clippy --workspace --all-targets -- -D warnings",
"test": "bun run test:rust && bun run test:node && bun run test:python",
"test": "bun run test:rust && bun run test:c && bun run test:node && bun run test:python",
"test:rust": "bun scripts/test-rust.ts",
"test:node": "bun test",
"test:python": "cd py-test && uv run --locked pytest",
"test:c": "bun scripts/test-c.ts",
"test:testcase": "cargo test -p braillify test_by_testcase -- --nocapture",
"preinstall": "uv sync && (cargo install wasm-pack || node -e \"process.exit(0)\") && (pip install maturin || \"process.exit(0)\")",
"build": "cargo build --release -p braillify && bun -F braillify build && cd packages/python && maturin build --release --out dist",
"build": "cargo build --release -p braillify && cargo build --release -p braillify-c && bun -F braillify build && cd packages/python && maturin build --release --out dist",
"build:landing": "bun run build && (cargo test test_by_testcase || node -e \"process.exit(0)\") && bun -F landing build",
"changepacks": "bunx @changepacks/cli",
"dev": "bun -F landing dev"
Expand Down
2 changes: 2 additions & 0 deletions packages/c/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/tests/smoke

16 changes: 16 additions & 0 deletions packages/c/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "braillify-c"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
description = "C ABI bindings for Braillify"
license = "MIT OR Apache-2.0"
repository = "https://github.com/dev-five-git/braillify"

[lib]
name = "braillify_c"
crate-type = ["cdylib", "staticlib", "rlib"]
doctest = false

[dependencies]
braillify = { path = "../../libs/braillify", default-features = false }
23 changes: 23 additions & 0 deletions packages/c/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
TARGET_DIR ?= ../../target
PROFILE ?= debug
LIB_DIR := $(TARGET_DIR)/$(PROFILE)
UNAME_S := $(shell uname -s)

ifeq ($(UNAME_S),Darwin)
RPATH_FLAG := -Wl,-rpath,@loader_path/../../../target/$(PROFILE)
else
RPATH_FLAG := -Wl,-rpath,'$$ORIGIN/../../../target/$(PROFILE)'
endif

.PHONY: build test clean

build:
cargo build -p braillify-c

test: build
$(CC) -std=c11 -Wall -Wextra -Werror -Iinclude tests/smoke.c \
-L$(LIB_DIR) -lbraillify_c $(RPATH_FLAG) -o tests/smoke
./tests/smoke

clean:
$(RM) tests/smoke
66 changes: 66 additions & 0 deletions packages/c/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# braillify-c

한국어 텍스트를 2024 개정 한국 점자 규정에 따라 변환하는 Braillify의 C 바인딩입니다. 동일한 헤더를 C와 C++에서 사용할 수 있습니다.

## 빌드

저장소 루트에서 동적·정적 라이브러리를 빌드합니다.

```bash
cargo build --release -p braillify-c
```

빌드 결과는 플랫폼에 따라 `target/release/libbraillify_c.so`, `libbraillify_c.dylib`, `braillify_c.dll` 및 정적 라이브러리로 생성됩니다. 공개 헤더는 `include/braillify.h`입니다.

## 사용법

```c
#include <braillify.h>
#include <stdio.h>

int main(void) {
char *result = braillify_encode_unicode("안녕하세요");
if (result == NULL) {
char *error = braillify_last_error();
fprintf(stderr, "%s\n", error != NULL ? error : "unknown error");
braillify_string_free(error);
return 1;
}

puts(result);
braillify_string_free(result);
return 0;
}
```

Linux에서는 다음과 같이 링크할 수 있습니다.

```bash
cc example.c -Ipath/to/packages/c/include -Lpath/to/target/release \
-lbraillify_c -o example
```

동적 라이브러리를 실행 시 검색할 수 있도록 `LD_LIBRARY_PATH`(Linux), `DYLD_LIBRARY_PATH`(macOS) 또는 `PATH`(Windows)를 설정하거나 애플리케이션에 rpath를 지정해야 합니다.

## API와 메모리 소유권

- 입력은 NUL로 끝나는 UTF-8 문자열이어야 합니다.
- `braillify_encode_unicode`와 `braillify_encode_braille_font`의 반환값은 `braillify_string_free`로 해제합니다.
- `braillify_encode`의 반환값은 함께 받은 길이를 그대로 사용해 `braillify_bytes_free`로 해제합니다.
- 실패 시 인코딩 함수는 `NULL`을 반환합니다. `braillify_last_error`가 반환한 메시지도 `braillify_string_free`로 해제합니다.
- 마지막 오류는 스레드별로 저장되며, 다음 인코딩 호출이 시작되면 초기화됩니다.
- 모든 해제 함수는 `NULL`을 허용합니다.

## C++

헤더가 선언을 `extern "C"`로 감싸므로 C++에서도 그대로 포함할 수 있습니다. 반환된 메모리는 `delete`나 `free`가 아니라 반드시 위의 Braillify 해제 함수로 반환해야 합니다.

## 테스트

```bash
cargo test -p braillify-c
make -C packages/c test
bun run test:c
```

`make` 명령은 실제 C11 컴파일러로 공개 헤더와 동적 라이브러리를 링크해 스모크 테스트를 실행합니다. `bun run test:c`는 같은 공개 헤더를 C11과 C++17로 각각 컴파일하고 라이브러리에 링크한 뒤 두 실행 파일을 모두 검증합니다.
41 changes: 41 additions & 0 deletions packages/c/include/braillify.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef BRAILLIFY_H
#define BRAILLIFY_H

#include <stddef.h>
#include <stdint.h>

#if defined(_WIN32) && defined(BRAILLIFY_SHARED)
# if defined(BRAILLIFY_BUILD)
# define BRAILLIFY_API __declspec(dllexport)
# else
# define BRAILLIFY_API __declspec(dllimport)
# endif
#else
# define BRAILLIFY_API
#endif

#ifdef __cplusplus
extern "C" {
#endif

/*
* All input strings must be NUL-terminated UTF-8. Returned pointers are owned
* by the caller and must be released with the matching function below.
* On failure, encoding functions return NULL and braillify_last_error()
* returns a newly allocated description for the calling thread.
*/

BRAILLIFY_API uint8_t *braillify_encode(const char *text, size_t *out_len);
BRAILLIFY_API char *braillify_encode_unicode(const char *text);
BRAILLIFY_API char *braillify_encode_braille_font(const char *text);
BRAILLIFY_API char *braillify_last_error(void);

BRAILLIFY_API void braillify_bytes_free(uint8_t *bytes, size_t len);
BRAILLIFY_API void braillify_string_free(char *value);

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* BRAILLIFY_H */

Loading
Loading