diff --git a/NativeScript/CMakeLists.txt b/NativeScript/CMakeLists.txt index 4cd4aa21..de245bad 100644 --- a/NativeScript/CMakeLists.txt +++ b/NativeScript/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.15) # Metadata - project(NativeScript CXX OBJCXX) set(NAME NativeScript) @@ -17,7 +16,6 @@ set(COMMON_FLAGS "-O3 -Wno-shorten-64-to-32") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS}") # Arguments - set(TARGET_PLATFORM "macos" CACHE STRING "Target platform for the Objective-C bridge") set(TARGET_ENGINE "v8" CACHE STRING "Target JS engine for the NativeScript runtime") set(METADATA_SIZE 0 CACHE STRING "Size of embedded metadata in bytes") @@ -56,6 +54,10 @@ elseif(TARGET_PLATFORM STREQUAL "macos") set(TARGET_PLATFORM_SPEC "macos-arm64_x86_64") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTARGET_PLATFORM_MACOS") + # Unlike "ios", "macos" doesn't seem to produce a NativeScript.framework.dSYM + # by default. We can produce it by setting these variables. + set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym") + set(CMAKE_XCODE_ATTRIBUTE_DWARF_DSYM_FOLDER_PATH "${CMAKE_CURRENT_BINARY_DIR}/$") else() message(FATAL_ERROR "Unknown target platform: ${TARGET_PLATFORM}") return() @@ -87,18 +89,16 @@ endif() if(ENABLE_JS_RUNTIME) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_JS_RUNTIME") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_JS_RUNTIME") -elseif(TARGET_PLATFORM_MACOS) - # If building a generic library for macOS, we'll build a dylib instead of a framework - unset(BUILD_FRAMEWORK) +else() set(GENERIC_NAPI TRUE) endif() message(STATUS "TARGET_PLATFORM = ${TARGET_PLATFORM}") message(STATUS "TARGET_ENGINE = ${TARGET_ENGINE}") message(STATUS "ENABLE_JS_RUNTIME = ${ENABLE_JS_RUNTIME}") +message(STATUS "GENERIC_NAPI = ${GENERIC_NAPI}") # Set up sources - include_directories( ./ ../metadata-generator/include @@ -201,12 +201,14 @@ if(ENABLE_JS_RUNTIME) ) set(SOURCE_FILES ${SOURCE_FILES} + # quickjs napi/quickjs/source/cutils.c napi/quickjs/source/libregexp.c napi/quickjs/source/libbf.c napi/quickjs/source/libunicode.c napi/quickjs/source/quickjs.c + # napi napi/quickjs/quickjs-api.c napi/quickjs/jsr.cpp @@ -223,7 +225,6 @@ if(ENABLE_JS_RUNTIME) napi/jsc/jsc-api.cpp napi/jsc/jsr.cpp ) - endif() else() include_directories( @@ -239,7 +240,6 @@ if(BUILD_CLI_BINARY) endif() # Find SDK - find_program(XCODEBUILD_EXECUTABLE xcodebuild) if(METADATA_SIZE EQUAL 0) @@ -265,7 +265,6 @@ set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") message(STATUS "SDK = ${CMAKE_OSX_SYSROOT}") # Build targets - if(BUILD_CLI_BINARY) add_executable(${NAME} ${SOURCE_FILES}) else() @@ -301,11 +300,22 @@ endif() if(TARGET_PLATFORM_MACOS) # add_custom_command(TARGET ${NAME} POST_BUILD - # COMMAND /usr/libexec/PlistBuddy -c "Add :LSMinimumSystemVersion string ${CMAKE_OSX_DEPLOYMENT_TARGET}" $/Resources/Info.plist + # COMMAND /usr/libexec/PlistBuddy -c "Add :LSMinimumSystemVersion string ${CMAKE_OSX_DEPLOYMENT_TARGET}" $/Resources/Info.plist # ) + # Convert the Info.plist from binary format to XML format. + # This seemed to unblock a build error when using in React Native. + add_custom_command(TARGET ${NAME} POST_BUILD + COMMAND plutil -convert xml1 $/Resources/Info.plist + ) set(METADATA_FILE "metadata.macos.nsmd") elseif(TARGET_PLATFORM_IOS) + # Convert the Info.plist from binary format to XML format. + # This seemed to unblock a build error when using in React Native. + add_custom_command(TARGET ${NAME} POST_BUILD + COMMAND plutil -convert xml1 $/Info.plist + ) + if(TARGET_PLATFORM_SIM) set(METADATA_FILE "metadata.ios-sim.nsmd") else() @@ -362,7 +372,7 @@ if(TARGET_ENGINE_JSC) endif() if(TARGET_ENGINE_V8) - if (TARGET_PLATFORM_MACOS) + if(TARGET_PLATFORM_MACOS) target_link_directories( ${NAME} PRIVATE @@ -414,12 +424,12 @@ if(TARGET_ENGINE_QUICKJS) ) endif() -# if (GENERIC_NAPI) -target_link_options( - ${NAME} - PRIVATE - "-Wl" - "-undefined" - "dynamic_lookup" -) -# endif() +if(GENERIC_NAPI) + target_link_options( + ${NAME} + PRIVATE + "-Wl" + "-undefined" + "dynamic_lookup" + ) +endif() diff --git a/build_all_ios.sh b/build_all_ios.sh index 0e880034..4089f5af 100755 --- a/build_all_ios.sh +++ b/build_all_ios.sh @@ -1,5 +1,29 @@ #!/bin/bash set -e +source "$(dirname "$0")/build_utils.sh" + +TARGET_ENGINE=${TARGET_ENGINE:=some} +BUILD_IPHONE=$(to_bool ${BUILD_IPHONE:=true}) +BUILD_SIMULATOR=$(to_bool ${BUILD_SIMULATOR:=true}) +BUILD_MACOS=$(to_bool ${BUILD_MACOS:=false}) +EMBED_METADATA=$(to_bool ${EMBED_METADATA:=false}) + +# See build_nativescript.sh for all supported flags. This parent script is only +# interested in intercepting a subset of them. +for arg in $@; do + case $arg in + --v8|--quickjs|--jsc|--hermes) TARGET_ENGINE=some ;; + --sim|--simulator) BUILD_SIMULATOR=true ;; + --no-sim|--no-simulator) BUILD_SIMULATOR=false ;; + --iphone|--device) BUILD_IPHONE=true ;; + --no-iphone|--no-device) BUILD_IPHONE=false ;; + --macos) BUILD_MACOS=true ;; + --no-macos) BUILD_MACOS=false ;; + --no-engine) TARGET_ENGINE=none ;; + --embed-metadata) EMBED_METADATA=true ;; + *) ;; + esac +done rm -rf ./dist # don't run if NO_UPDATE_VERSION is set @@ -8,7 +32,37 @@ rm -rf ./dist # ./update_version.sh # fi ./build_metadata_generator.sh -./build_nativescript.sh --no-vision $1 $2 -./build_tklivesync.sh --no-vision -./prepare_dSYMs.sh -./build_npm_ios.sh \ No newline at end of file + +if $EMBED_METADATA; then + checkpoint "Generating metadata, as --embed-metadata was passed..." + + if $BUILD_IPHONE; then + checkpoint "Generating metadata for iOS (physical device)..." + npm run metagen ios + fi + if $BUILD_SIMULATOR; then + checkpoint "Generating metadata for iOS (simulator)..." + npm run metagen ios-sim + fi + if $BUILD_MACOS; then + checkpoint "Generating metadata for macOS..." + npm run metagen macos + fi + + checkpoint "... All metadata generated!" +fi + +./build_nativescript.sh --no-vision $1 $2 $3 $4 $5 $6 $7 $8 $9 + +if [[ "$TARGET_ENGINE" == "none" ]]; then + # If you're building *with* --no-engine, you're trying to make an npm release + # of a workspace under ./packages/*, like @nativescript/ios-node-api. + echo "Skipping build_npm_ios.sh due to --no-engine flag." + echo "build_all_ios.sh finished!" +else + # If you're building *without* --no-engine, you're trying to make an npm + # release of the root-level workspace, @nativescript/ios. + ./build_tklivesync.sh --no-vision + ./prepare_dSYMs.sh + ./build_npm_ios.sh +fi \ No newline at end of file diff --git a/build_all_react_native.sh b/build_all_react_native.sh new file mode 100755 index 00000000..eaf683f1 --- /dev/null +++ b/build_all_react_native.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +./build_all_ios.sh --no-engine --embed-metadata --macos diff --git a/build_nativescript.sh b/build_nativescript.sh index de3dc0de..c9e58ec8 100755 --- a/build_nativescript.sh +++ b/build_nativescript.sh @@ -2,27 +2,6 @@ set -e source "$(dirname "$0")/build_utils.sh" -function to_bool() { - local arg="$1" - case "$(echo "$arg" | tr '[:upper:]' '[:lower:]')" in - [0-9]+) - if [ $arg -eq 0 ]; then - echo false - else - echo true - fi - ;; - n|no|f|false) echo false ;; - y|yes|t|true) echo true ;; - * ) - if [ -n "$arg" ]; then - echo "warning: invalid boolean argument ('$arg'). Expected true or false" >&2 - fi - echo false - ;; - esac; -} - BUILD_CATALYST=$(to_bool ${BUILD_CATALYST:=false}) # disable by default for now BUILD_IPHONE=$(to_bool ${BUILD_IPHONE:=true}) BUILD_SIMULATOR=$(to_bool ${BUILD_SIMULATOR:=true}) @@ -74,6 +53,8 @@ mkdir -p $DIST/intermediates function cmake_build () { local platform="$1" + shift + local archs=("$@") local is_macos_cli=false if [ "$platform" == "macos-cli" ]; then @@ -85,7 +66,7 @@ function cmake_build () { if $EMBED_METADATA || $is_macos_cli; then - for arch in x86_64 arm64; do + for arch in "${archs[@]}"; do METADATA_SIZE=$(($METADATA_SIZE > $(stat -f%z "./metadata-generator/metadata/metadata.$platform.$arch.nsmd") ? $METADATA_SIZE : $(stat -f%z "./metadata-generator/metadata/metadata.$platform.$arch.nsmd"))) @@ -101,42 +82,40 @@ function cmake_build () { if $BUILD_CATALYST; then checkpoint "Building NativeScript for Mac Catalyst" -# cmake_build catalyst +# cmake_build catalyst x86_64 arm64 fi if $BUILD_SIMULATOR; then -checkpoint "Building NativeScript for iphone simulators (multi-arch)" +checkpoint "Building NativeScript for iPhone (simulator)" -cmake_build ios-sim +cmake_build ios-sim x86_64 arm64 fi if $BUILD_IPHONE; then -checkpoint "Building NativeScript for ARM64 device" +checkpoint "Building NativeScript for iPhone (physical)" -cmake_build ios +cmake_build ios arm64 fi if $BUILD_MACOS; then checkpoint "Building NativeScript for macOS" -cmake_build macos - -cp "$DIST/intermediates/macos/$CONFIG_BUILD/libNativeScript.dylib" "$DIST/../packages/macos/dist/macos/NativeScript.node" +cmake_build macos x86_64 arm64 fi if $BUILD_VISION; then -checkpoint "Building NativeScript for visionOS Device" +checkpoint "Building NativeScript for visionOS (physical)" -# cmake_build visionos +# cmake_build visionos arm64 -checkpoint "Building NativeScript for visionOS Simulators" +checkpoint "Building NativeScript for visionOS (simulator)" -# cmake_build visionos-sim +# cmake_build visionos-sim x86_64 arm64 fi @@ -144,7 +123,7 @@ if $BUILD_MACOS_CLI; then checkpoint "Building NativeScript for macOS CLI" -cmake_build macos-cli +cmake_build macos-cli x86_64 arm64 fi @@ -164,7 +143,6 @@ if $BUILD_IPHONE; then -debug-symbols "$DIST/intermediates/ios/$CONFIG_BUILD-iphoneos/NativeScript.framework.dSYM" ) fi - if $BUILD_VISION; then XCFRAMEWORKS+=( -framework "$DIST/intermediates/visionos/$CONFIG_BUILD-xros/NativeScript.framework" -debug-symbols "$DIST/intermediates/visionos/$CONFIG_BUILD-xros/NativeScript.framework.dSYM" ) @@ -174,20 +152,44 @@ if $BUILD_VISION; then fi if [[ -n "${XCFRAMEWORKS[@]}" ]]; then - -checkpoint "Creating NativeScript.xcframework" -OUTPUT_DIR="$DIST/NativeScript.xcframework" -rm -rf $OUTPUT_DIR -xcodebuild -create-xcframework ${XCFRAMEWORKS[@]} -output "$OUTPUT_DIR" - + if [[ "$TARGET_ENGINE" == "none" ]]; then + checkpoint "Creating the XCFramework for iOS (NativeScript.apple.node)" + + # We adhere to the prebuilds standard as described here: + # https://github.com/callstackincubator/react-native-node-api/blob/9b231c14459b62d7df33360f930a00343d8c46e6/docs/PREBUILDS.md + OUTPUT_DIR="packages/ios/build/$CONFIG_BUILD/NativeScript.apple.node" + rm -rf $OUTPUT_DIR + deno run -A ./scripts/build_xcframework.mts --output "$OUTPUT_DIR" ${XCFRAMEWORKS[@]} + else + checkpoint "Creating NativeScript.xcframework" + + OUTPUT_DIR="$DIST/NativeScript.xcframework" + rm -rf $OUTPUT_DIR + xcodebuild -create-xcframework ${XCFRAMEWORKS[@]} -output "$OUTPUT_DIR" + fi fi +# We're currently distributing two separate packages: +# 1. UIKit-based (@nativescript/ios-node-api) +# 2. AppKit-based (@nativescript/macos-node-api) +# As such, there's no point bundling both UIKit-based and AppKit-based into a +# single XCFramework. if $BUILD_MACOS; then - -checkpoint "Creating NativeScript.node" - -cp -r "$DIST/intermediates/macos/$CONFIG_BUILD/libNativeScript.dylib" "$DIST/NativeScript.node" - + XCFRAMEWORKS=( -framework "$DIST/intermediates/macos/$CONFIG_BUILD/NativeScript.framework" + -debug-symbols "$DIST/intermediates/macos/$CONFIG_BUILD/NativeScript.framework.dSYM" ) + + if [[ "$TARGET_ENGINE" == "none" ]]; then + checkpoint "Creating the XCFramework for macOS (NativeScript.apple.node)" + + # We adhere to the prebuilds standard as described here: + # https://github.com/callstackincubator/react-native-node-api/blob/9b231c14459b62d7df33360f930a00343d8c46e6/docs/PREBUILDS.md + OUTPUT_DIR="packages/macos/build/$CONFIG_BUILD/NativeScript.apple.node" + rm -rf $OUTPUT_DIR + deno run -A ./scripts/build_xcframework.mts --output "$OUTPUT_DIR" ${XCFRAMEWORKS[@]} + else + checkpoint "Creating NativeScript.node for macOS" + cp -r "$DIST/intermediates/macos/$CONFIG_BUILD/libNativeScript.dylib" "$DIST/NativeScript.node" + fi fi if $BUILD_MACOS_CLI; then diff --git a/build_tklivesync.sh b/build_tklivesync.sh index e24a0e3b..b17dc546 100755 --- a/build_tklivesync.sh +++ b/build_tklivesync.sh @@ -2,27 +2,6 @@ set -e source "$(dirname "$0")/build_utils.sh" -function to_bool() { - local arg="$1" - case "$(echo "$arg" | tr '[:upper:]' '[:lower:]')" in - [0-9]+) - if [ $arg -eq 0 ]; then - echo false - else - echo true - fi - ;; - n|no|f|false) echo false ;; - y|yes|t|true) echo true ;; - * ) - if [ -n "$arg" ]; then - echo "warning: invalid boolean argument ('$arg'). Expected true or false" >&2 - fi - echo false - ;; - esac; -} - BUILD_CATALYST=$(to_bool ${BUILD_CATALYST:=true}) BUILD_IPHONE=$(to_bool ${BUILD_IPHONE:=true}) BUILD_SIMULATOR=$(to_bool ${BUILD_SIMULATOR:=true}) diff --git a/build_utils.sh b/build_utils.sh index 5b60fcf1..e3bece00 100644 --- a/build_utils.sh +++ b/build_utils.sh @@ -6,6 +6,27 @@ set -e # source "$(dirname "$0")/build_utils.sh" # +function to_bool() { + local arg="$1" + case "$(echo "$arg" | tr '[:upper:]' '[:lower:]')" in + [0-9]+) + if [ $arg -eq 0 ]; then + echo false + else + echo true + fi + ;; + n|no|f|false) echo false ;; + y|yes|t|true) echo true ;; + * ) + if [ -n "$arg" ]; then + echo "warning: invalid boolean argument ('$arg'). Expected true or false" >&2 + fi + echo false + ;; + esac; +} + # Prints a timestamp + title for a step/section function checkpoint { local delimiter="--------------------------------------------------------------------------------" diff --git a/packages/ios/.gitignore b/packages/ios/.gitignore new file mode 100644 index 00000000..684ae5d8 --- /dev/null +++ b/packages/ios/.gitignore @@ -0,0 +1 @@ +!build \ No newline at end of file diff --git a/packages/ios/NativeScript.podspec b/packages/ios/NativeScript.podspec deleted file mode 100644 index 51a19515..00000000 --- a/packages/ios/NativeScript.podspec +++ /dev/null @@ -1,14 +0,0 @@ -require "json" - -package = JSON.parse(File.read(File.join(__dir__, "package.json"))) - -Pod::Spec.new do |s| - s.name = "NativeScript" - s.version = package['version'] - s.summary = "An embeddable, engine-agnostic NativeScript runtime for iOS based on Node-API" - s.homepage = "https://github.com/NativeScript/runtime-node-api.git" - s.author = "DjDeveloperr", "Jamie Birch" - s.source = { git: '' } - s.vendored_frameworks = "build/ios-universal/NativeScript.xcframework" - s.platform = :ios, '13.4' -end diff --git a/packages/ios/index.d.ts b/packages/ios/index.d.ts index 9ebe90fa..a9b51353 100644 --- a/packages/ios/index.d.ts +++ b/packages/ios/index.d.ts @@ -1,2 +1,20 @@ /// /// + +/** + * The function to initialize all the JS bindings to the iOS SDK, allowing you + * to call iOS APIs from JS (e.g. `NSString.alloc().init()`). + * + * For NativeScript apps (i.e. apps using NativeScript Core), don't call this! + * The initialization will be done for you under-the-hood from the native side. + * + * For React Native apps, and any other hosts that implement the + * `react-native-node-api` [prebuilds standard](https://github.com/callstackincubator/react-native-node-api/blob/9b231c14459b62d7df33360f930a00343d8c46e6/docs/PREBUILDS.md), + * call this before calling any iOS APIs from JS. It's simplest to call it early + * on in the entrypoint of your app. + * + * It's not safe to call this API twice - so if you put it in a useEffect() + * hook, be careful about the hook getting called twice due to a HMR update or + * React Strict Mode. + */ +export function init(): void; diff --git a/packages/ios/index.js b/packages/ios/index.js new file mode 100644 index 00000000..c8eb0611 --- /dev/null +++ b/packages/ios/index.js @@ -0,0 +1,34 @@ +if (typeof interop === "undefined") { + // deno-lint-ignore no-process-globals + if (process) { + // === + // If we're in a Node-like environment (e.g. Node.js for Mobile) + // === + + const path = + "./build/RelWithDebInfo/NativeScript.apple.node/ios-arm64/NativeScript.framework/NativeScript"; + + let metaURL = import.meta.url; + if (!metaURL.includes("://")) { + metaURL = "file://" + metaURL; + } + + const module = { exports: {} }; + + // deno-lint-ignore no-process-globals + process.dlopen(module, new URL(path, metaURL).pathname); + + module.exports.init( + // deno-lint-ignore no-process-globals + process.env.METADATA_PATH + ); + } else { + // === + // If we're in a React Native-like environment + // === + + // react-native-node-api/babel-plugin will rewrite this to: + // module.exports = require("react-native-node-api").requireNodeAddon("-nativescript-macos-node-api—-NativeScript"); + module.exports = require("./build/RelWithDebInfo/NativeScript.apple.node"); + } +} diff --git a/packages/ios/package.json b/packages/ios/package.json index d6cc1db5..e0de7f3d 100644 --- a/packages/ios/package.json +++ b/packages/ios/package.json @@ -4,15 +4,14 @@ "description": "An embeddable, engine-agnostic NativeScript runtime for iOS based on Node-API", "repository": { "type": "git", - "url": "https://github.com/NativeScript/runtime-node-api.git", + "url": "https://github.com/NativeScript/napi-ios.git", "directory": "packages/ios" }, "typings": "index.d.ts", "type": "module", "files": [ - "dist/ios-universal/NativeScript.xcframework", + "build/RelWithDebInfo/NativeScript.apple.node", "index.d.ts", - "NativeScript.podspec", "types" ], "scripts": { @@ -22,6 +21,7 @@ "keywords": [ "iOS", "NativeScript", + "React Native", "UIKit" ], "contributors": [ @@ -43,6 +43,14 @@ } ], "license": "MIT", + "peerDependencies": { + "react-native-node-api": "*" + }, + "peerDependenciesMeta": { + "react-native-node-api": { + "optional": true + } + }, "dependencies": { "@nativescript/objc-node-api": ">=1.0.0-alpha.5" }, diff --git a/packages/macos/.gitignore b/packages/macos/.gitignore new file mode 100644 index 00000000..684ae5d8 --- /dev/null +++ b/packages/macos/.gitignore @@ -0,0 +1 @@ +!build \ No newline at end of file diff --git a/packages/macos/index.d.ts b/packages/macos/index.d.ts index dc6efcac..c627af64 100644 --- a/packages/macos/index.d.ts +++ b/packages/macos/index.d.ts @@ -1,5 +1,3 @@ /// import "@nativescript/objc-node-api"; - -export * from "./lib/native.js"; diff --git a/packages/macos/index.js b/packages/macos/index.js index f46bb814..c90918b2 100644 --- a/packages/macos/index.js +++ b/packages/macos/index.js @@ -1 +1,34 @@ -export * from "./lib/native.js"; +if (typeof interop === "undefined") { + // deno-lint-ignore no-process-globals + if (process) { + // === + // If we're in a Node-like environment (e.g. Node.js, Deno, or Bun) + // === + + const path = + "./build/RelWithDebInfo/NativeScript.apple.node/macos-arm64_x86_64/NativeScript.framework/Versions/0.1.0/NativeScript"; + + let metaURL = import.meta.url; + if (!metaURL.includes("://")) { + metaURL = "file://" + metaURL; + } + + const module = { exports: {} }; + + // deno-lint-ignore no-process-globals + process.dlopen(module, new URL(path, metaURL).pathname); + + module.exports.init( + // deno-lint-ignore no-process-globals + process.env.METADATA_PATH + ); + } else { + // === + // If we're in a React Native-like environment + // === + + // react-native-node-api/babel-plugin will rewrite this to: + // module.exports = require("react-native-node-api").requireNodeAddon("-nativescript-macos-node-api—-NativeScript"); + module.exports = require("./build/RelWithDebInfo/NativeScript.apple.node"); + } +} diff --git a/packages/macos/lib/native.js b/packages/macos/lib/native.js deleted file mode 100644 index 1c2c33f1..00000000 --- a/packages/macos/lib/native.js +++ /dev/null @@ -1,35 +0,0 @@ -let path = "../dist/macos/NativeScript.node"; - -if (typeof Deno == "object") { - const execPath = Deno.execPath(); - const parts = execPath.split("/"); - parts.pop(); // remove executable name - const containerDir = parts.pop(); - if (containerDir === "MacOS" && parts.pop() === "Contents") { - const resourcesDir = parts.join("/") + "/Contents/Frameworks"; - const newPath = resourcesDir + "/libNativeScript.dylib"; - if (Deno.statSync(newPath).isFile) { - path = newPath; - } - } -} - -if (typeof interop === "undefined") { - let metaURL = import.meta.url; - if (!metaURL.includes("://")) { - metaURL = "file://" + metaURL; - } - - const module = { exports: {} }; - - // deno-lint-ignore no-process-globals - process.dlopen( - module, - new URL(path, metaURL).pathname, - ); - - module.exports.init( - // deno-lint-ignore no-process-globals - process.env.METADATA_PATH, - ); -} diff --git a/packages/macos/package.json b/packages/macos/package.json index 9a76641f..49d75507 100644 --- a/packages/macos/package.json +++ b/packages/macos/package.json @@ -4,16 +4,15 @@ "description": "An embeddable, engine-agnostic NativeScript runtime for macOS based on Node-API", "repository": { "type": "git", - "url": "https://github.com/NativeScript/runtime-node-api.git", + "url": "https://github.com/NativeScript/napi-ios.git", "directory": "packages/macos" }, "main": "index.js", "typings": "index.d.ts", "type": "module", "files": [ - "dist/macos/NativeScript.node", + "build/RelWithDebInfo/NativeScript.apple.node", "index.d.ts", - "lib", "types" ], "scripts": { @@ -51,4 +50,4 @@ "devDependencies": { "rimraf": "^6.0.0" } -} \ No newline at end of file +} diff --git a/scripts/build_xcframework.mts b/scripts/build_xcframework.mts new file mode 100644 index 00000000..120ba4d2 --- /dev/null +++ b/scripts/build_xcframework.mts @@ -0,0 +1,124 @@ +import path from "node:path"; +import process from "node:process"; +import yargsParser from "yargs-parser"; +import { createXCframework } from "react-native-node-api"; + +main(); + +/** + * Builds an XCFramework in the style that `react-native-node-api` expects, + * following the [prebuilds standard](https://github.com/callstackincubator/react-native-node-api/blob/9b231c14459b62d7df33360f930a00343d8c46e6/docs/PREBUILDS.md). + * + * This is really just an XCFramework renamed with a `.node` extension, + * containing an extra file named `react-native-api-module`. Once `cmake-rn` + * supports it, we may be able to replace this whole script with a simple + * `cmake-rn` call. + */ +async function main() { + const __dirname = import.meta.dirname; + if (!__dirname) { + throw new Error("Expected import.meta.dirname to be truthy."); + } + + const monorepoRoot = path.resolve(__dirname, ".."); + + // Args are designed to match the names of the args from cmake we're + // interested in. + const args = yargsParser(process.argv.slice(2), { + configuration: { + "short-option-groups": false, + }, + string: ["output", "framework", "debug-symbols"], + array: ["framework", "debug-symbols"], + boolean: ["help"], + alias: { + h: "help", + }, + default: { + help: false, + }, + }); + + const { + output, + framework: frameworks = new Array(), + "debug-symbols": debugSymbols = new Array(), + help, + } = args; + + const helpText = ` +Typical usage: + +$ deno run -A ./scripts/build_xcframework.mts --output packages/ios/build/Release/NativeScript.apple.node -framework dist/intermediates/ios/RelWithDebInfo-iphoneos/NativeScript.framework -debug-symbols dist/intermediates/ios/RelWithDebInfo-iphoneos/NativeScript.framework.dSYM + +$ deno run -A ./scripts/build_xcframework.mts --output packages/macos/build/Release/NativeScript.apple.node -framework dist/intermediates/macos/RelWithDebInfo/NativeScript.framework -debug-symbols dist/intermediates/macos/RelWithDebInfo/NativeScript.framework.dSYM +`.trim(); + + if (help) { + console.log(helpText); + process.exit(0); + } + + if (!output) { + console.log( + `Please specify the output directory by passing the \`--output\` arg.\n\n${helpText}` + ); + process.exit(1); + } + + if (!isStringArray(frameworks) || !isStringArray(debugSymbols)) { + console.log( + `Expected -framework and -debug-symbols to be parsed as arrays.\n\n${helpText}` + ); + process.exit(1); + } + + if (!frameworks?.length) { + console.log( + `Please specify at least one framework to bundle into the xcframework by passing the \`-framework\` arg one or more times.\n\n${helpText}` + ); + process.exit(1); + } + + const outputPath = path.resolve(monorepoRoot, output); + const frameworkPaths = frameworks.map((framework) => + path.resolve(monorepoRoot, framework) + ); + // TODO: Pass this to the createXCFramework() options, once supported. + const _dsymPaths = + debugSymbols?.map((dsym) => path.resolve(monorepoRoot, dsym)) ?? []; + + try { + await createXCframework({ + outputPath, + frameworkPaths, + // dsymPaths, + autoLink: true, + }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : ""; + console.error(`Failed to assemble XCFramework: ${errorMessage}`, error); + process.exit(1); + } + + console.log( + `\x1b[32m✔\x1b[0m XCFramework assembled into \x1b[2m${path.relative( + monorepoRoot, + outputPath + )}\x1b[22m` + ); +} + +function isStringArray(value: unknown): value is Array { + if (!Array.isArray(value)) { + return false; + } + + for (const element of value) { + if (typeof element !== "string") { + return false; + } + } + + return true; +} diff --git a/scripts/package.json b/scripts/package.json index 95d55652..907b361c 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -11,6 +11,10 @@ "type": "commonjs", "description": "", "dependencies": { - "@nativescript/core": "^8.8.6" + "@nativescript/core": "^8.8.6", + "yargs-parser": "^22.0.0" + }, + "devDependencies": { + "react-native-node-api": "^0.3.2" } }