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
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,36 @@ jobs:
run: |-
POD_VERSION=0.0.1-for-ci-test PRIMJS_COMMIT_ID=${{ github.event.pull_request.head.sha }} bundle exec pod spec lint PrimJS.podspec --verbose --skip-import-validation --allow-warnings

swift-package:
runs-on: macos-latest
steps:
- name: Download Source
uses: actions/checkout@v4.2.2
- name: Check generated SwiftPM headers
run: python3 tools/ios_tools/generate_spm_headers.py --check
- name: Build and test (macOS)
run: |-
swift build
swift test
- name: Build and test with Debugger trait (macOS)
run: |-
swift build --traits Debugger
swift test --traits Debugger
- name: Build and test (iOS Simulator)
run: |-
UDID=$(xcrun simctl list devices available -j | python3 -c '
import json, sys
devices = json.load(sys.stdin)["devices"]
runtimes = sorted((r for r in devices if "iOS" in r), reverse=True)
print(next(d["udid"] for r in runtimes for d in devices[r] if d["name"].startswith("iPhone")))')
xcodebuild test -scheme PrimJS-Package \
-destination "platform=iOS Simulator,id=$UDID" \
-derivedDataPath .build/DerivedData -quiet
- name: Build (iOS device)
run: |-
xcodebuild build -scheme PrimJS-Package -destination 'generic/platform=iOS' \
-derivedDataPath .build/DerivedData -quiet

check-unittests-linux:
runs-on: lynx-ubuntu-22.04-medium
steps:
Expand Down
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
out/
out/
.DS_Store

# Swift Package Manager
.build/
.swiftpm/
Package.resolved
DerivedData/
183 changes: 183 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// swift-tools-version: 6.1
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
//
// Mirrors PrimJS.podspec; keep the two in sync.
//
// PrimJS/quickjs + PrimJS/log -> PrimJS
// PrimJS/quickjs_debugger -> `Debugger` trait (PrimJSInspector)
// PrimJS/napi/{core,env,quickjs} -> PrimJSNAPI
// PrimJS/napi/jsc -> PrimJSNAPIJSC
// PrimJS/napi/adapter -> PrimJSNAPIAdapter
//
// Public headers live in swiftpm/headers/<Target>/, generated by
// tools/ios_tools/generate_spm_headers.py with the CocoaPods layout so include
// paths are the same for both package managers. arm64 only, like the podspec.

import PackageDescription

// GCC_PREPROCESSOR_DEFINITIONS of the pod target and the `quickjs` subspec.
// They change struct layouts in quickjs-inner.h, so every target uses the
// same list.
let podDefines: [CXXSetting] = [
.define("OS_IOS", to: "1"),
.define("ENABLE_CODECACHE"),
.define("ENABLE_VIRTUAL_STACK", to: "1"),
.define("ENABLE_PRIMJS_SNAPSHOT"),
.define("ENABLE_COMPATIBLE_MM"),
.define("ENABLE_LEPUSNG"),
.define("LYNX_SIMPLIFY", to: "0"),
// Like the quickjs_debugger subspec, this switches the whole engine.
.define("ENABLE_QUICKJS_DEBUGGER", .when(traits: ["Debugger"])),
]

let podHeaderSearchPaths: [CXXSetting] = [
.headerSearchPath("src"),
.headerSearchPath("src/interpreter"),
]

// The napi sources include their headers by bare name.
let napiHeaderSearchPaths: [CXXSetting] = [
.headerSearchPath("src/napi"),
.headerSearchPath("src/napi/common"),
.headerSearchPath("src/napi/env"),
.headerSearchPath("src/napi/quickjs"),
.headerSearchPath("src/napi/adapter"),
]

let package = Package(
name: "PrimJS",
platforms: [
.iOS(.v12),
// Same configuration as iOS; used for `swift build` / `swift test`.
.macOS(.v10_15),
],
products: [
.library(name: "PrimJS", targets: ["PrimJS"]),
.library(name: "PrimJSNAPI", targets: ["PrimJSNAPI"]),
.library(name: "PrimJSNAPIJSC", targets: ["PrimJSNAPIJSC"]),
.library(name: "PrimJSNAPIAdapter", targets: ["PrimJSNAPIAdapter"]),
],
traits: [
.trait(
name: "Debugger",
description:
"Build the engine with the Chrome DevTools Protocol debugger "
+ "(PrimJS/quickjs_debugger). Changes the engine ABI."),
],
targets: [
.target(
name: "PrimJS",
dependencies: [
.target(name: "PrimJSInspector", condition: .when(traits: ["Debugger"]))
],
path: ".",
exclude: [
"src/gc/BUILD.gn",
"src/interpreter/quickjs/BUILD.gn",
],
sources: [
"src/gc",
"src/interpreter/quickjs",
// embedded.S / embedded-inspector.S guard on ENABLE_QUICKJS_DEBUGGER.
"src/interpreter/primjs/ios",
"src/basic/log",
],
publicHeadersPath: "swiftpm/headers/PrimJS",
cxxSettings: podDefines + podHeaderSearchPaths
),

// Always built with the debugger ABI; linked only under the trait. The
// engine and the inspector reference each other; the linker resolves it.
.target(
name: "PrimJSInspector",
path: ".",
exclude: [
"src/inspector/BUILD.gn"
],
sources: [
"src/inspector"
],
publicHeadersPath: "swiftpm/headers/PrimJSInspector",
cxxSettings: podDefines + podHeaderSearchPaths + [
.define("ENABLE_QUICKJS_DEBUGGER")
]
),

.target(
name: "PrimJSNAPI",
dependencies: ["PrimJS"],
path: ".",
exclude: [
"src/napi/common/BUILD.gn",
"src/napi/env/BUILD.gn",
"src/napi/quickjs/BUILD.gn",
],
sources: [
// The podspec glob src/napi/*.{h,cc} is non-recursive.
"src/napi/napi.cc",
"src/napi/napi_module.cc",
"src/napi/common",
"src/napi/env",
"src/napi/quickjs",
],
publicHeadersPath: "swiftpm/headers/PrimJSNAPI",
cxxSettings: podDefines + podHeaderSearchPaths + napiHeaderSearchPaths
),

.target(
name: "PrimJSNAPIJSC",
dependencies: ["PrimJSNAPI", "PrimJS"],
path: ".",
exclude: [
"src/napi/jsc/BUILD.gn"
],
sources: [
"src/napi/jsc"
],
publicHeadersPath: "swiftpm/headers/PrimJSNAPIJSC",
cxxSettings: podDefines + podHeaderSearchPaths + napiHeaderSearchPaths + [
.define("JSC_OBJC_API_ENABLED", to: "0")
],
linkerSettings: [
.linkedFramework("JavaScriptCore")
]
),

.target(
name: "PrimJSNAPIAdapter",
dependencies: ["PrimJSNAPI"],
path: ".",
sources: [
// weak_napi_host_injector.cc belongs to the LynxWeakNodeAPI pod.
"src/napi/adapter/js_native_api_adapter.cc",
"src/napi/adapter/weak_napi_host_generator.cc",
],
publicHeadersPath: "swiftpm/headers/PrimJSNAPIAdapter",
cxxSettings: podDefines + podHeaderSearchPaths + napiHeaderSearchPaths
),

.testTarget(
name: "PrimJSTests",
dependencies: ["PrimJS", "PrimJSNAPI"],
path: "swiftpm/Tests/PrimJSTests",
cxxSettings: [
// Users of the Node-API headers must define this themselves.
.define("ENABLE_CODECACHE")
],
linkerSettings: [
// Only linked implicitly for Swift test targets.
.linkedFramework("Foundation"),
.linkedFramework("XCTest"),
]
),

.testTarget(
name: "PrimJSSwiftTests",
dependencies: ["PrimJS"],
path: "swiftpm/Tests/PrimJSSwiftTests"
),
],
cxxLanguageStandard: .gnucxx17
)
26 changes: 26 additions & 0 deletions swiftpm/Tests/PrimJSSwiftTests/PrimJSSwiftTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

// Swift consumer: `import PrimJS` goes through the generated Clang module
// (swiftpm/headers/PrimJS/module.modulemap), which exposes the C API only.

import PrimJS
import XCTest

final class PrimJSSwiftTests: XCTestCase {
func testEvaluatesScript() throws {
let runtime = try XCTUnwrap(LEPUS_NewRuntime())
defer { LEPUS_FreeRuntime(runtime) }
let context = try XCTUnwrap(LEPUS_NewContext(runtime))
defer { LEPUS_FreeContext(context) }

let script = "const add = (a, b) => a + b; add(40, 2)"
let value = LEPUS_Eval(context, script, script.utf8.count, "<PrimJSSwiftTests>", 0)
XCTAssertEqual(LEPUS_IsException(value), 0)

var result: Int32 = 0
XCTAssertEqual(LEPUS_ToInt32(context, &result, value), 0)
XCTAssertEqual(result, 42)
}
}
65 changes: 65 additions & 0 deletions swiftpm/Tests/PrimJSTests/PrimJSTests.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

// Smoke test for the Swift Package Manager build. `swift build` only archives
// the library targets, so this is what links the pre-generated snapshot
// (embedded.S) and runs the template interpreter. Includes use the CocoaPods
// paths reproduced by swiftpm/headers.

#import <XCTest/XCTest.h>

#include "napi.h"
#include "napi_env.h"
#include "napi_env_quickjs.h"
#include "quickjs/include/quickjs.h"
#include "quickjs/include/trace-gc.h"

@interface PrimJSTests : XCTestCase
@end

@implementation PrimJSTests {
LEPUSRuntime *_runtime;
LEPUSContext *_context;
}

- (void)setUp {
_runtime = LEPUS_NewRuntime();
_context = LEPUS_NewContext(_runtime);
}

- (void)tearDown {
LEPUS_FreeContext(_context);
LEPUS_FreeRuntime(_runtime);
}

- (void)testEvaluatesScript {
// ENABLE_COMPATIBLE_MM (from the podspec) means tracing GC plus the
// template interpreter, i.e. the embedded snapshot is exercised here.
XCTAssertTrue(LEPUS_IsGCMode(_context));

const char *script = "let sum = 0; for (let i = 0; i < 1000; i++) sum += i; sum";
HandleScope scope(_context);
LEPUSValue value = LEPUS_Eval(_context, script, strlen(script), "<test>",
LEPUS_EVAL_TYPE_GLOBAL);
scope.PushHandle(&value, HANDLE_TYPE_LEPUS_VALUE);
XCTAssertFalse(LEPUS_IsException(value));

int32_t result = 0;
XCTAssertEqual(LEPUS_ToInt32(_context, &result, value), 0);
XCTAssertEqual(result, 499500);
}

- (void)testNodeAPIOnQuickJS {
napi_env env = napi_new_env();
napi_attach_quickjs(env, _context);
{
Napi::HandleScope scope(env);
Napi::Value result = Napi::Env(env).RunScript("6 * 7");
XCTAssertEqual(result.As<Napi::Number>().Int32Value(), 42);
}
napi_detach_quickjs(env);
napi_free_env(env);
}

@end
18 changes: 18 additions & 0 deletions swiftpm/headers/PrimJS/PrimJS.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

// Umbrella header of the `PrimJS` Clang module (see module.modulemap). It
// lists the C API that is importable from Swift and Objective-C; the C++
// headers next to it stay textual includes.

#ifndef SWIFTPM_HEADERS_PRIMJS_PRIMJS_H_
#define SWIFTPM_HEADERS_PRIMJS_PRIMJS_H_

// quickjs.h is written for C++, where `bool` is built in. When the module is
// built as C (Swift importer, Objective-C) it comes from here.
#include <stdbool.h>

#include "quickjs/include/quickjs.h"

#endif // SWIFTPM_HEADERS_PRIMJS_PRIMJS_H_
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/basic/log/logging.h
10 changes: 10 additions & 0 deletions swiftpm/headers/PrimJS/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

// Without this file SwiftPM would treat the whole directory, C++ headers
// included, as the module and `import PrimJS` would fail.
module PrimJS {
umbrella header "PrimJS.h"
export *
}
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/alloc_config.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/alloc_utils.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/allocator.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/base_export.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/bignum.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/cartesian_tree.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/collector.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/collector_ms.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/cutils.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/deque.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/dtoa.h
1 change: 1 addition & 0 deletions swiftpm/headers/PrimJS/quickjs/include/gc_marking.h
Loading