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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ jobs:
run: |
ctest --test-dir build --output-on-failure

- if: ${{ matrix.os != 'windows-latest' }}
name: Swift Test
run: |
swift test -v

aarch64-smoke:
runs-on: ubuntu-latest
name: Aarch64 cross-compile smoke test
Expand Down
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ config.h
/CMakeFiles/
/cmake_install.cmake

# Scratch folders
.scratch/

# test suite
test/genkeystroke
test/performance
Expand Down Expand Up @@ -132,6 +135,7 @@ contrib/test.sqlite3

# OS X
.DS_Store
.Spotlight-V100

# python
*.pyc
Expand All @@ -144,4 +148,10 @@ contrib/test.sqlite3

# fuzzer
/fuzzer/in
/fuzzer/out
/fuzzer/out

# Swift
./[Bb]uild/
.build/
.swiftpm/
index-build/
19 changes: 19 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"makefile.extensionOutputFolder": "./.vscode",
"makefile.configureOnOpen": false,

"files.exclude": {
"./Build": true,
".build": true,
".swiftpm": true,
"**/.build": true,
"**/.swiftpm": true
},
"search.exclude": {
"./Build": true,
".build": true,
".swiftpm": true,
"**/.build": true,
"**/.swiftpm": true
}
}
44 changes: 44 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// swift-tools-version:6.1
import PackageDescription

let package = Package(
name: "libchewing",
products: [
.library(
name: "Chewing",
targets: ["CChewing"]
)
],
targets: [
// Expose the existing C public headers in `capi/include` as a Clang module
// Attach the `CargoBuild` plugin which runs `cargo build` before linking.
.target(
name: "CChewing",
path: "capi",
publicHeadersPath: "include",
// Instruct the linker to search common Cargo target dirs where the built library may be placed
linkerSettings: [
.unsafeFlags([
"-L",
".build/plugins/outputs/libchewing/CChewing/destination/CargoBuild/cargo-target/release",
]),
.linkedLibrary("chewing_capi"),
],
plugins: [
.plugin(name: "CargoBuild")
]
),
// Build-tool plugin that invokes cargo to produce the static library
.plugin(
name: "CargoBuild",
capability: .buildTool(),
path: "swift/tools/CargoBuildPlugin"
),
// Swift test target in `swift/unit_tests` to validate C API accessibility from Swift
.testTarget(
name: "ChewingTests",
dependencies: ["CChewing"],
path: "swift/unit_tests"
),
]
)
69 changes: 69 additions & 0 deletions swift/tools/CargoBuildPlugin/Plugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Foundation
import PackagePlugin

@main
struct CargoBuildPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
// Paths
let packageDir = context.package.directoryURL
let capiDir = packageDir.appendingPathComponent("capi")
// Use the plugin's work directory (sandboxed) to avoid macOS permission issues
let scratchTarget = context.pluginWorkDirectoryURL.appendingPathComponent("cargo-target")

// Auto-build is enabled by default. To disable automatic Cargo build, set `LIBCHEWING_AUTO_BUILD_CARGO=0`.
if ProcessInfo.processInfo.environment["LIBCHEWING_AUTO_BUILD_CARGO"] == "0" {
return []
}

// Prefer common cargo install locations; if not found, abort with a clear error
let fm = FileManager.default
let home = fm.homeDirectoryForCurrentUser.path
let candidates: [URL] = [
URL(fileURLWithPath: "/usr/bin/cargo"),
URL(fileURLWithPath: "/usr/local/bin/cargo"),
URL(fileURLWithPath: "/opt/homebrew/bin/cargo"),
URL(fileURLWithPath: "\(home)/.cargo/bin/cargo"),
]

var cargoURL: URL? = nil
for url in candidates {
if fm.fileExists(atPath: url.path) {
cargoURL = url
break
}
}

guard let cargo = cargoURL else {
struct UserError: Error, CustomStringConvertible {
let description: String
init(_ s: String) { description = s }
}
throw UserError(
"`cargo` not found on the system. Please install Rust (https://rustup.rs/) to enable automatic builds, or disable automatic Cargo build by setting `LIBCHEWING_AUTO_BUILD_CARGO=0` and run `swift/scripts/build-cargo.sh` manually to produce the library before running `swift build`."
)
}

let manifestPath = capiDir.appendingPathComponent("Cargo.toml").path
let targetDir = scratchTarget.path

// Arguments: build the chewing_capi crate in release mode into the plugin workdir target
let args = [
"build",
"--release",
"--manifest-path",
manifestPath,
"--target-dir",
targetDir,
]

return [
.prebuildCommand(
displayName: "Building chewing_capi via cargo",
executable: cargo,
arguments: args,
environment: [:],
outputFilesDirectory: scratchTarget
)
]
}
}
40 changes: 40 additions & 0 deletions swift/unit_tests/ChewingTests_Suite1.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation
import Testing

@testable import CChewing

@MainActor
struct ChewingTestsSuite1 {
@Test func testCreateDelete() {
let ctx = chewing_new()
#expect(ctx != nil, "chewing_new() should return non-null context")
if ctx != nil {
chewing_delete(ctx)
}
}

@Test func testDefaultDictionaryNames() {
let names = chewing_get_defaultDictionaryNames()
#expect(names != nil, "chewing_get_defaultDictionaryNames() should return non-null")
if let names = names {
let s = String(cString: names)
#expect(!s.isEmpty, "Default dictionary names should not be empty")
}
}

@Test func testSetGetKBType() {
let ctx = chewing_new()
guard let ctx else {
#expect(ctx != nil, "chewing_new() should return non-null context")
return
}
defer { chewing_delete(ctx) }

// KB_HSU == 1 according to C header enum
let rc = chewing_set_KBType(ctx, 1)
#expect(rc == 0, "chewing_set_KBType should return 0 on success")

let kb = chewing_get_KBType(ctx)
#expect(kb == 1, "chewing_get_KBType should reflect set value")
}
}
Loading
Loading