Static and dynamic macOS/iOS XCFramework packaging for upstream librime.
This repository builds a binary distribution so Xcode and SwiftPM consumers do not need to compile librime and its C++ dependency graph themselves.
A release contains:
librime-static.xcframework.ziplibrime-dynamic.xcframework.zipLICENSE.txtTHIRD_PARTY_NOTICES.mdthird-party-notices.zipbuild-metadata.json
The static XCFramework contains macOS arm64/x86_64, iOS device arm64, and iOS simulator arm64/x86_64 library slices. The dynamic XCFramework contains RimeDynamic.framework slices for macOS, iOS device, and iOS simulator. The macOS slice is a versioned deep bundle (Versions/A, install name @rpath/RimeDynamic.framework/Versions/A/RimeDynamic) so SwiftPM-embedded copies already satisfy macOS app validation without a post-embed fix-up script. Both include the public librime C API headers but no module maps; the Rime Swift module is provided by the package's headers product. GitHub Releases exposes the SHA-256 digest for each uploaded asset.
Release tags contain a generated Package.swift with a headers target and binary targets that point at the matching GitHub Release assets.
.package(url: "https://github.com/ghostflyby/librime-xcframework.git", from: "1.16.1-pack.1")All consumer source code writes import Rime. The Rime module is declared once, by product Rime's headers target; the linking products below supply only the binary to link, not a module consumers import:
Rime— public librime headers plus theRimemodule only, shipped as a source-free systemLibrary: no binaries are downloaded, nothing is linked, and nothing is compiled on the consumer side. Wrapper libraries should depend on this product.RimeDynamic— the dynamic framework XCFramework. Xcode links and embeds it automatically for targets that declare the product.RimeDynamicStub— a link-only handle for the dynamic framework. It ships no binaries and exposes no API; each platform carries a skeletonRimeDynamic.frameworkin the SDK style (thetapi stubifystub sits at the binary's position), and targets that declare the product get-framework RimeDynamicfrom the package's linker settings with no embedded framework copy. XPC services and app extensions use it to share the app's single embeddedRimeDynamic.framework(see below).RimeStatic— the static XCFramework with librime dependencies merged into the archive. Linked automatically.RimeSystem— binds against a system-provided or user-replaced librime implementation viapkg-config rimeflags without distributing any librime headers. It also declares theRimemodule, so do not combineRimeSystemandRimein the same package graph.
Wrapper libraries depend on Rime only. Terminal apps that use the binary artifacts depend on Rime plus exactly one of RimeDynamic/RimeStatic (linked automatically, or manually with pkg-config rime flags). Apps binding a system librime depend on RimeSystem alone — RimeSystem replaces Rime in the graph, never combines with it.
Xcode embeds the RimeDynamic product into every target that declares it and offers no "link only" switch. Extension-like targets should declare RimeDynamicStub instead of RimeDynamic; the target is then linked against the framework by name while nothing is embedded. Wire the loader to the app's embedded copy:
-
Declare
RimeDynamicStubon the XPC/extension target (alongsideRimeor a wrapper library that already provides it). -
Put the skeleton framework directory on the framework search path for the matching SDK. The skeletons are generated and committed by the release pipeline per platform under
Sources/RimeDynamicStubin the package checkout; for a macOS XPC:FRAMEWORK_SEARCH_PATHS[sdk=macosx*] = $(BUILD_DIR)/../../SourcePackages/checkouts/librime-xcframework/Sources/RimeDynamicStub/macosCondition the path per SDK (
macosx*/iphoneos*/iphonesimulator*selecting themacos/ios/ios-simulatorskeleton) and do not use one recursive path over all platforms: the skeletons share the framework name, and the linker should only be offered the skeleton whose target triples match the platform being linked. -
Point the runpath at the app's embedded copy. For a macOS XPC service four levels up reaches the app's
Frameworksdirectory:LD_RUNPATH_SEARCH_PATHS = @executable_path/../../../../Frameworks
The skeletons are generated with tapi stubify from the released dylibs by the release pipeline and committed with the release manifest — the repository carries no hand-made stubs — so a skeleton always matches the artifacts of its tag. A mismatched skeleton fails loudly — at link time if the skeleton is older than the framework, at launch if it is newer.
RimeDynamicStub fixes a contract, not an implementation. Declaring it emits -framework RimeDynamic — a load command for @rpath/RimeDynamic.framework/Versions/A/RimeDynamic plus a symbol requirement equal to the skeleton's exported list — and nothing else: whatever framework dyld resolves under that install name at runtime is the implementation. A wrapper library that links through the stub (directly, or through a package trait such as RimeKit's librimeDynamic) therefore leaves the choice of the librime binary to the final app:
- Released binary (default) — declare
RimeDynamicon the app target to embed the packaged framework. - Custom or patched librime — repackage the replacement under the same identity: bundle name
RimeDynamic.framework, versioned layoutVersions/A/RimeDynamic, install name@rpath/RimeDynamic.framework/Versions/A/RimeDynamic(set withinstall_name_tool -id; the replacement's own dependencies must remain resolvable). Embed and sign it in place of the packaged copy — the wrapper library needs no rebuild. The replacement's exported symbols must cover the skeleton's list of the tag the library was built against; a framework older than the skeleton fails at link time, a newer one fails at launch. - System librime (e.g. Homebrew) — a bare
librime.dylibis not a drop-in for the stub: its install name differs. Either wrap it into the framework identity above, or use theRimeSystemproduct at the package-graph level (pkg-config), which replacesRimeand cannot coexist with it.
User data directories (RimeTraits) are independent of the binary: swapping the implementation does not touch deployed schemas or user configuration.
Releases up to 1.17.0-pack.6 shipped the Rime headers product as a compiled Clang target. When a scheme with code coverage enabled built a test graph sharing that product between an app host and a test bundle, Xcode built the product as a dynamic framework whose link failed with Undefined symbols: ___llvm_profile_runtime: coverage instrumentation references the profile runtime from every translation unit (even empty or data-only ones), and the dynamic product framework link omits it.
The headers product now compiles nothing on the consumer side, so coverage builds are unaffected from the release that carries this change on. Since 1.17.0-pack.8 the RimeDynamicStub placeholder translation unit is Swift for the same reason: its product variant links the profile runtime through the Swift driver, while a C translation unit inside such a variant fails the same way. For older releases, scope the scheme's coverage targets to your own targets or disable coverage for the affected scheme; normal app and extension builds without coverage are unaffected.
The modules previously shipped as RimeStatic, RimeDynamic, and RimeSystem; import sites must change to import Rime starting with the first release built from this layout.
Prerequisites:
- macOS with Xcode command line tools
- CMake and Ninja
- vcpkg, with
VCPKG_ROOTpointing at the vcpkg checkout - upstream
librimesource at../librimeorvendor/librime
Build and package:
VCPKG_ROOT=/path/to/vcpkg scripts/build-all.shBuild one slice:
VCPKG_ROOT=/path/to/vcpkg scripts/build-one-arch.sh arm64
VCPKG_ROOT=/path/to/vcpkg scripts/build-one-arch.sh x86_64Package existing slice outputs:
scripts/package-xcframework.shOutputs are written to out/ and dist/.
Package versions use:
<upstream-version>-pack.<packaging-revision>
In the build workflow, leaving upstream_ref empty builds the latest upstream release tag. Leaving packaging_version empty derives the release tag from the upstream version and packaging_revision; if packaging_revision is also empty, the workflow uses the next available pack revision.
Example:
1.16.1-pack.1
The packaging wrapper code in this repository is licensed under the BSD
3-Clause License. Binary release artifacts include upstream librime, which is
also BSD 3-Clause licensed, and may include statically linked third-party
dependencies resolved by vcpkg.
Keep LICENSE.txt, THIRD_PARTY_NOTICES.md, and third-party-notices.zip
with redistributed binary artifacts.