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
62 changes: 62 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -723,3 +723,65 @@ jobs:
with:
name: nuget-packages
path: packages/dotnet/**/nupkg/*.nupkg

# go
go-test:
name: Go Test - ${{ matrix.platform }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
Comment on lines +728 to +732

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

changepacks가 action에서 어떻게 돌아가는지 확인해보실 필요가 있을 것 같습니다

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

본 cicd에서는 deploy까지 포함되어야 합니다

matrix:
include:
# Windows x64
- runner: windows-latest
target: x86_64-pc-windows-gnu
platform: windows-amd64
# Linux x64
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux-amd64
# Linux ARM64
- runner: ubuntu-latest
target: aarch64-unknown-linux-gnu
platform: linux-arm64
# macOS ARM64
- runner: macos-14
target: aarch64-apple-darwin
platform: darwin-arm64
# macOS x64
- runner: macos-13
target: x86_64-apple-darwin
platform: darwin-amd64
steps:
- uses: actions/checkout@v5

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

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

- uses: actions/setup-go@v5
with:
go-version: '1.21'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

latest 옵션이 없는지 확인이 필요합니다

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

좋은 의견 감사합니다. 확인해보니 actions/setup-go에서 stable alias를 지원하여 최신 안정 버전을 자동으로 사용하도록 변경하겠습니다.


- name: Build native library
run: cargo build --release --target ${{ matrix.target }} -p braillify-go
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc

- name: Copy native library
run: |
mkdir -p packages/go/libs/${{ matrix.platform }}
cp target/${{ matrix.target }}/release/libbraillify_go.a packages/go/libs/${{ matrix.platform }}/
shell: bash

- name: Test
run: go test -v ./...
working-directory: packages/go
Comment on lines +785 to +787

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

test 이후 배포까지 필요합니다
별도의 패키지 저장소가 있는지 확인이 필요하며 없다면 어떤식으로 배포가 진행되는지 확인이 필요합니다

@kdyann kdyann Jun 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

현재 Go 바인딩이 CGo(Rust 코드 재사용 위해)를 통해 Rust 정적 라이브러리(.a)를 링크하는 구조입니다. 이 경우 일반적인 Go 패키지처럼 go get만으로 사용하기가 어려워 GitHub Release Asset에 플랫폼별 .a 파일을 업로드하고 사용자가 다운로드하여 사용하는 방식이 있습니다.
아니면 Rust 로직을 순수 Go로 포팅하여(WASM 사용) CGo 의존성을 제거하고 go get을 완전히 지원하도록 수정하는게 좋을지 여쭙고 싶습니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

우선 로직을 분할 하는 것은 완전한 안티패턴입니다, 재구현은 안됩니다

저도 go를 조금 공부해보니 release에 태그로 업로드하는 것만으로도 패키지 매니저에 잡히는 것 같네요

모든 OS에 따른 정적라이브러리를 포함시킬지 아니면 별도 분리할지는 trend에 따라 다를 것 같긴한데, 이 부분에 대한 의견이 궁금합니다

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

말씀해주신 대로 Rust 코어 로직을 Go로 재구현하는 방향은 제외하겠습니다.

정적 라이브러리 포함 여부를 생각해봤을 때, Go는 별도의 패키지 저장소 없이 GitHub 저장소의 소스코드를 직접 가져옵니다. Release 태그에 .a 파일을 별도로 업로드할 경우, go get 명령어가 해당 바이너리를 자동으로 다운로드해주지 않아 두 방향이 있는 것 같습니다.

  1. 통합 방식: 플랫폼별 .a 파일들을 Git 저장소에 직접 커밋합니다. 저장소 용량은 커지지만, 사용자는 일반 Go 패키지처럼 go get만으로 즉시 사용할 수 있습니다.

  2. 분리 방식: .a 파일은 GitHub Release에 올리고 Git에는 소스코드만 둡니다. 저장소는 가볍지만, 사용자는 go get 후 별도의 다운로드 스크립트를 실행해야 하는 번거로움이 생깁니다.

개인적으로는 현재 .a 파일 크기와 저장소 히스토리 관리 측면을 고려했을 때 2번 방식이 더 적합해보이는데, 이 방향으로 진행해도 괜찮을지 의견부탁드립니다!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2번 방식에서 추가적인 별도의 다운로드스크립트를 진행하는 것이 어느정도의 불편함을 가져 오는지 확인할 필요가 있습니다

이 부분에 대하여 측정된 정보나 스크립트에 대한 정보가 있을까요?

@kdyann kdyann Jun 13, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

현재 cgo가 ${SRCDIR}/libs/... 경로를 기준으로 정적 라이브러리를 찾고 있는데, go get으로 받은 패키지는 Go module cache에 들어가며 이 경로가 읽기 전용이 됩니다. 그래서 Release에서 .a를 받아도 해당 위치에 배치하기 어렵고, 별도 writable 경로를 사용하려면 사용자가 환경변수를 추가로 설정해야 합니다.

그래서 2번 방식은 사용성 측면과 복잡성에서 좋지 않은 것 같습니다.

사용자 경험 측면에서 1번 방식이 더 적합해 보입니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

그러면 1번으로 진행을 하고, go 언어의 경우 CICD 단계에서 추가적인 커밋이 더 붙어서 빌드 후에 정적 라이브러리들이 커밋이 되도록 개선함이 어떨까 싶습니다

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

넵 해당 방향으로 진행해보겠습니다!

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.

4 changes: 4 additions & 0 deletions packages/go/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.test
*.exe
*.out
libs/
11 changes: 11 additions & 0 deletions packages/go/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "braillify-go"
version = "2.0.0"
edition = "2024"

[lib]
name = "braillify_go"
crate-type = ["cdylib", "staticlib"]

[dependencies]
braillify = { path = "../../libs/braillify", default-features = false }
16 changes: 16 additions & 0 deletions packages/go/braillify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package braillify

// Encode converts Korean text to braille byte representation.
func Encode(text string) ([]byte, error) {
return cEncode(text)
}

// EncodeToUnicode converts Korean text to braille Unicode string.
func EncodeToUnicode(text string) (string, error) {
return cEncodeToUnicode(text)
}

// EncodeToBrailleFont converts Korean text to braille font string.
func EncodeToBrailleFont(text string) (string, error) {
return cEncodeToBrailleFont(text)
}
51 changes: 51 additions & 0 deletions packages/go/braillify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package braillify

import "testing"

func TestEncodeToUnicode(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"안녕하세요", "⠣⠒⠉⠻⠚⠠⠝⠬"},
{"상상이상의", "⠇⠶⠇⠶⠕⠇⠶⠺"},
{"1,000", "⠼⠁⠂⠚⠚⠚"},
{"ATM", "⠠⠠⠁⠞⠍"},
{"", ""},
}

for _, tt := range tests {
result, err := EncodeToUnicode(tt.input)
if err != nil {
t.Errorf("EncodeToUnicode(%q): unexpected error: %v", tt.input, err)
continue
}
t.Logf("EncodeToUnicode(%q) = %q", tt.input, result)
if result != tt.expected {
t.Errorf("EncodeToUnicode(%q) = %q, want %q", tt.input, result, tt.expected)
}
}
}

func TestEncode(t *testing.T) {
result, err := Encode("안녕")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Logf("Encode(%q) = %v", "안녕", result)
if len(result) == 0 {
t.Error("expected non-empty byte slice")
}
}

func TestEncodeToBrailleFont(t *testing.T) {
result, err := EncodeToBrailleFont("안녕하세요")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := "⠣⠒⠉⠻⠚⠠⠝⠬"
t.Logf("EncodeToBrailleFont(%q) = %q", "안녕하세요", result)
if result != expected {
t.Errorf("EncodeToBrailleFont = %q, want %q", result, expected)
}
}
85 changes: 85 additions & 0 deletions packages/go/cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package braillify

/*
#cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/libs/darwin-amd64 -lbraillify_go -lm -lpthread
#cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/libs/darwin-arm64 -lbraillify_go -lm -lpthread
#cgo linux,amd64 LDFLAGS: -L${SRCDIR}/libs/linux-amd64 -lbraillify_go -lm -lpthread -ldl
#cgo linux,arm64 LDFLAGS: -L${SRCDIR}/libs/linux-arm64 -lbraillify_go -lm -lpthread -ldl
#cgo windows,amd64 LDFLAGS: -L${SRCDIR}/libs/windows-amd64 -lbraillify_go -lntdll -lws2_32 -lbcrypt -ladvapi32 -luserenv

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

extern uint8_t* braillify_encode(const char* text, size_t* out_len);
extern char* braillify_encode_to_unicode(const char* text);
extern char* braillify_encode_to_braille_font(const char* text);
extern char* braillify_get_last_error();
extern void braillify_free_string(char* ptr);
extern void braillify_free_bytes(uint8_t* ptr, size_t len);
*/
import "C"

import (
"errors"
"runtime"
"unsafe"
)

func cEncode(text string) ([]byte, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

cText := C.CString(text)
defer C.free(unsafe.Pointer(cText))

var outLen C.size_t
result := C.braillify_encode(cText, &outLen)
if result == nil {
return nil, getLastError()
}
defer C.braillify_free_bytes(result, outLen)

return C.GoBytes(unsafe.Pointer(result), C.int(outLen)), nil
}

func cEncodeToUnicode(text string) (string, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

cText := C.CString(text)
defer C.free(unsafe.Pointer(cText))

result := C.braillify_encode_to_unicode(cText)
if result == nil {
return "", getLastError()
}
defer C.braillify_free_string(result)

return C.GoString(result), nil
}

func cEncodeToBrailleFont(text string) (string, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

cText := C.CString(text)
defer C.free(unsafe.Pointer(cText))

result := C.braillify_encode_to_braille_font(cText)
if result == nil {
return "", getLastError()
}
defer C.braillify_free_string(result)

return C.GoString(result), nil
}

func getLastError() error {
errPtr := C.braillify_get_last_error()
if errPtr == nil {
return errors.New("braillify: unknown error")
}
defer C.braillify_free_string(errPtr)
return errors.New(C.GoString(errPtr))
}
3 changes: 3 additions & 0 deletions packages/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/dev-five-git/braillify/packages/go

go 1.21
Loading
Loading