[Feature] Add Swift Package Manager support - #376
Open
OhKanghoon wants to merge 6 commits into
Open
Conversation
OhKanghoon
force-pushed
the
feature/swift-package-manager
branch
3 times, most recently
from
August 20, 2026 15:21
43ea51d to
246649d
Compare
Ignore .build/, .swiftpm/, Package.resolved and DerivedData/ ahead of adding Package.swift.
Swift Package Manager allows a single public header directory per target and cannot rename or re-root headers. tools/ios_tools/generate_spm_headers.py therefore writes, for each SwiftPM target, a directory of relative symlinks under swiftpm/headers/<target>/. The layout follows the CocoaPods rules for `public_header_files`, `header_dir` and `header_mappings_dir` (Headers/Public/PrimJS/...), so the include paths are the ones CocoaPods users have always used: `quickjs/include/quickjs.h`, `quickjs/include/trace-gc.h`, `napi_env_quickjs.h`, `basic/log/logging.h`. The generated tree is identical to what `pod install` produces for the same subspecs. The script is plain Python with a declarative table of what each target publishes; it does not depend on CocoaPods so the SwiftPM setup keeps working after the podspec is gone. `--check` verifies the committed tree is up to date and is run in CI.
Add Package.swift mapping PrimJS.podspec 1:1 onto SwiftPM targets, using
the existing sources, preprocessor definitions and header search paths
from the original src/ tree:
PrimJS/quickjs + log -> PrimJS
PrimJS/quickjs_debugger -> `Debugger` trait (PrimJSInspector)
PrimJS/napi/{core,env,quickjs} -> PrimJSNAPI
PrimJS/napi/jsc -> PrimJSNAPIJSC
PrimJS/napi/adapter -> PrimJSNAPIAdapter
Sources are declared per directory wherever the podspec uses a recursive
glob; the few files listed individually are the ones the podspec also
lists individually. Public headers come from the generated
swiftpm/headers/ directories, so include paths are the same as with
CocoaPods.
The debugger is a package trait rather than a separate product because,
exactly like adding the subspec to a pod target, ENABLE_QUICKJS_DEBUGGER
changes the engine ABI (quickjs-inner.h) and selects the inspector
snapshot (embedded-inspector.S) for every target. Both snapshots live in
the PrimJS target and guard themselves on that define.
As with the podspec, only arm64 is supported: the template interpreter
ships as pre-generated arm64 assembly.
`swift build` only archives the library targets. The test links the engine, including the pre-generated snapshot (embedded.S), evaluates a script on the template interpreter and runs one through the Node-API layer, using the CocoaPods include paths reproduced by swiftpm/headers. It also runs with `--traits Debugger`, which exercises the inspector snapshot and the engine <-> inspector link.
Verify that swiftpm/headers is up to date, build and test on macOS with and without the Debugger trait, test on the iOS Simulator and build for iOS devices.
Without a module map SwiftPM treats the whole public header directory as the module's umbrella, so `import PrimJS` fails on the first C++ header (logging.h, quickjs-inner.h, ...). CocoaPods has the same problem with `use_frameworks!`, whose umbrella imports every public header. Add a hand-written umbrella header, swiftpm/headers/PrimJS/PrimJS.h, that lists the C API (quickjs.h) and pulls in <stdbool.h> first, since quickjs.h is written for C++ where `bool` is built in, plus a module map that points at it. Objective-C++ consumers are unaffected: they never go through the module and keep including headers textually. The header generator now owns only the symlinks in swiftpm/headers and leaves regular files alone, so regenerating keeps the two files. A Swift test target imports the module and evaluates a script.
OhKanghoon
force-pushed
the
feature/swift-package-manager
branch
from
August 20, 2026 15:37
246649d to
7a5632f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds a
Package.swiftso PrimJS can be consumed with Swift Package Manager in addition to CocoaPods.What's in it
The manifest is a 1:1 mapping of
PrimJS.podspec. Sources, preprocessor definitions and header search paths are taken from the podspec and point at the existingsrc/tree; nothing is copied or wrapped. Sources are declared per directory wherever the podspec uses a recursive glob; the few files listed individually (napi.cc,napi_module.cc, the two adapter files) are the ones the podspec also lists individually, with the reason next to each.PrimJS/quickjs+PrimJS/logPrimJSPrimJS/quickjs_debuggerDebuggerpackage trait (internalPrimJSInspectortarget)PrimJS/napi/{core,env,quickjs}PrimJSNAPIPrimJS/napi/jscPrimJSNAPIJSCPrimJS/napi/adapterPrimJSNAPIAdapterPublic headers. SwiftPM allows a single public header directory per target and cannot rename or re-root headers, while CocoaPods assembles
Headers/Public/PrimJS/<header_dir>/<name>frompublic_header_files,header_dirandheader_mappings_dir.tools/ios_tools/generate_spm_headers.pywrites that same layout as relative symlinks underswiftpm/headers/<target>/, so the include paths are identical for both package managers (quickjs/include/quickjs.h,quickjs/include/trace-gc.h,napi_env_quickjs.h, ...). I checked the generated tree against whatpod installproduces for the same subspecs; all 65 entries match. The script is plain Python with a declarative table of what each target publishes and has no dependency on CocoaPods, so the SwiftPM setup keeps working after the podspec is gone. Its--checkmode runs in CI and fails with the list of stale/missing entries when a public header is added or moved without regenerating.Debugger.
quickjs_debuggeris a package trait instead of a product. Adding the subspec to a pod changesENABLE_QUICKJS_DEBUGGERfor the whole pod target, which changes struct layouts inquickjs-inner.hand switches the snapshot toembedded-inspector.S. A separate SwiftPM target layered on top of the plain engine would therefore link the wrong snapshot and see the wrong ABI; the trait reproduces the pod behaviour. This is the reason forswift-tools-version: 6.1(Xcode 16.3+).Swift. Without a module map SwiftPM treats the whole public header directory as the module's umbrella, so
import PrimJSwould fail on the first C++ header (the same happens with CocoaPodsuse_frameworks!, whose umbrella imports every public header). A hand-written umbrella header inswiftpm/headers/PrimJS/lists the C API (quickjs.h, preceded by<stdbool.h>since the header is written for C++) and a three-line module map points at it, so Swift can call the engine directly. Objective-C++ consumers are unaffected; they never go through the module. The header generator only manages the symlinks and leaves these two files alone.Tests.
swift buildonly archives the library targets, so a small XCTest target (one.mmfile) links the engine, evaluates a script on the template interpreter and runs one through the Node-API layer; a second one doesimport PrimJSfrom Swift. Both run with and without the trait.CI. A
swift-packagejob runs the header check,swift build/swift teston macOS with and withoutDebugger,xcodebuild teston the iOS Simulator and ageneric/platform=iOSbuild. It usesmacos-latest; happy to move it to the self-hosted darwin runners if that's preferred.Limitations
ENABLE_CODECACHEthemselves; SwiftPM can't propagate defines to dependents. Noted inPackage.swiftnext to the test target, which does exactly that.Verification
swift build,swift test, both with--traits Debuggeras well (macOS arm64, Xcode 26.4)xcodebuild test -scheme PrimJS-Packageon an iOS 27 simulator,xcodebuild build -destination generic/platform=iOSpython3 tools/ios_tools/generate_spm_headers.py --checkpod spec lintis unaffected: no existing file is modified except.gitignoreandci.yml