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
80 changes: 74 additions & 6 deletions NativeScript/NativeScript.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "NativeScript.h"
#include <Foundation/Foundation.h>
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <mach-o/getsect.h>
#include "inspector/JsV8InspectorClient.h"
#include "runtime/Console.h"
#include "runtime/Helpers.h"
Expand All @@ -26,7 +29,76 @@ @implementation Config

@implementation NativeScript

extern char defaultStartOfMetadataSection __asm("section$start$__DATA$__TNSMetadata");
static void* HostExecutableMetadata() {
// Debug builds with ENABLE_DEBUG_DYLIB link the app's code and linker flags
// into <executable>.debug.dylib and leave the executable a stub.
NSString* debugDylibName = [[[[NSBundle mainBundle] executablePath] lastPathComponent]
stringByAppendingString:@".debug.dylib"];
for (uint32_t i = 0, count = _dyld_image_count(); i < count; i++) {
const struct mach_header* header = _dyld_get_image_header(i);
if (header == nullptr) {
continue;
}
if (header->filetype != MH_EXECUTE) {
const char* path = _dyld_get_image_name(i);
const char* name = path != nullptr ? strrchr(path, '/') : nullptr;
if (debugDylibName == nil || name == nullptr ||
strcmp(name + 1, debugDylibName.UTF8String) != 0) {
continue;
}
}
unsigned long size = 0;
uint8_t* data = getsectiondata(reinterpret_cast<const struct mach_header_64*>(header), "__DATA",
"__TNSMetadata", &size);
// The app template creates an empty metadata file before the generator
// runs, so an empty section means the host carries no metadata.
if (data != nullptr && size > 0) {
return data;
}
}
return nullptr;
}

// NativeScriptDefaultMetadata.framework ships only with the NativeScriptSDK
// package product, embedded next to NativeScript.framework.
static void* DefaultFrameworkMetadata() {
const char* accessorName = "NativeScriptDefaultMetadata";
void* accessor = dlsym(RTLD_DEFAULT, accessorName);
if (accessor == nullptr) {
NSString* frameworksDir = [[[NSBundle bundleForClass:[NativeScript class]] bundlePath]
stringByDeletingLastPathComponent];
NSBundle* bundle =
[NSBundle bundleWithPath:[frameworksDir stringByAppendingPathComponent:
@"NativeScriptDefaultMetadata.framework"]];
// executablePath, unlike a hand-built path, follows Mac Catalyst's versioned layout.
NSString* executable = [bundle executablePath];
void* handle =
executable != nil ? dlopen(executable.fileSystemRepresentation, RTLD_NOW) : nullptr;
accessor = handle != nullptr ? dlsym(handle, accessorName) : nullptr;
}
if (accessor == nullptr) {
return nullptr;
}
return const_cast<void*>(reinterpret_cast<const void* (*)(void)>(accessor)());
}

static void* ResolveMetadataPtr(Config* config) {
if (config.MetadataPtr != nil) {
return config.MetadataPtr;
}
if (void* hostMetadata = HostExecutableMetadata()) {
return hostMetadata;
}
if (void* defaultMetadata = DefaultFrameworkMetadata()) {
return defaultMetadata;
}
std::string fatal =
"no NativeScript metadata found. Pass Config.MetadataPtr, link a __DATA,__TNSMetadata "
"section into the app executable (-sectcreate __DATA __TNSMetadata <metadata.bin>), or "
"depend on the NativeScriptSDK package product, which bundles default metadata.";
Log(@"Fatal: %s", fatal.c_str());
throw tns::NativeScriptException("Fatal: " + fatal);
}

- (void)runScriptString:(NSString*)script runLoop:(BOOL)runLoop {
std::string cppString = std::string([script UTF8String]);
Expand Down Expand Up @@ -165,11 +237,7 @@ - (instancetype)initializeWithConfig:(Config*)config {
RuntimeConfig.ApplicationPath =
[[config.BaseDir stringByAppendingPathComponent:@"app"] UTF8String];
}
if (config.MetadataPtr != nil) {
RuntimeConfig.MetadataPtr = [config MetadataPtr];
} else {
RuntimeConfig.MetadataPtr = &defaultStartOfMetadataSection;
}
RuntimeConfig.MetadataPtr = ResolveMetadataPtr(config);
RuntimeConfig.IsDebug = [config IsDebug];
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];

Expand Down
22 changes: 22 additions & 0 deletions NativeScriptDefaultMetadata/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
8 changes: 8 additions & 0 deletions NativeScriptDefaultMetadata/NativeScriptDefaultMetadata.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Linked with -sectcreate __DATA __TNSMetadata metadata-arm64.bin.
extern char startOfMetadataSection __asm("section$start$__DATA$__TNSMetadata");

// Looked up by name with dlsym from NativeScript.framework; keep the symbol
// name and signature stable.
__attribute__((visibility("default"), used)) const void* NativeScriptDefaultMetadata(void) {
return &startOfMetadataSection;
}
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,20 @@ package can be built in two modes (`--spm-mode`, see `./build_npm_ios.sh --help`
so a remote-mode package only resolves for versions that shipped through
`.github/workflows/npm_release.yml` — don't use this mode for local builds.

ios-spm exposes two iOS products:

- **`NativeScript`** — the runtime and TKLiveSync. {N} CLI apps use this product and link
the metadata generated for them into their own executable.
- **`NativeScriptSDK`** — additionally links `NativeScriptDefaultMetadata`, a separate
framework carrying a default snapshot of iOS SDK metadata, for apps that embed NativeScript
without generating metadata of their own. Built by `./build_default_metadata.sh` (iOS
family only; there is no visionOS build).

At startup the runtime uses `Config.MetadataPtr` if set, otherwise a `__DATA,__TNSMetadata`
section linked into the host executable, otherwise `NativeScriptDefaultMetadata.framework` if
it is embedded next to `NativeScript.framework`, and fails to start if none is found. The
embedded local package only serves CLI apps, so it does not include the default metadata.

# Local Development & Linking

To use a locally built runtime in an app, build the default (embedded) package and point
Expand Down
1 change: 1 addition & 0 deletions build_all_ios.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rm -rf ./dist
./build_metadata_generator.sh
./build_nativescript.sh --no-vision
./build_tklivesync.sh --no-vision
./build_default_metadata.sh
./prepare_dSYMs.sh
./build_spm_artifacts.sh ios
./build_npm_ios.sh "$@"
112 changes: 112 additions & 0 deletions build_default_metadata.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/bin/bash
set -e
source "$(dirname "$0")/build_utils.sh"

function to_bool() {
local arg="$1"
case "$(echo "$arg" | tr '[:upper:]' '[:lower:]')" in
n|no|f|false) echo false ;;
y|yes|t|true) echo true ;;
# case patterns are globs, not regexes: whatever gets past this arm is all digits
''|*[!0-9]*)
if [ -n "$arg" ]; then
echo "warning: invalid boolean argument ('$arg'). Expected true or false" >&2
fi
echo false
;;
* )
if [ "$arg" -eq 0 ]; then
echo false
else
echo true
fi
;;
esac;
}

BUILD_CATALYST=$(to_bool ${BUILD_CATALYST:=true})
BUILD_IPHONE=$(to_bool ${BUILD_IPHONE:=true})
BUILD_SIMULATOR=$(to_bool ${BUILD_SIMULATOR:=true})

for arg in $@; do
case $arg in
--catalyst|--maccatalyst) BUILD_CATALYST=true ;;
--no-catalyst|--no-maccatalyst) BUILD_CATALYST=false ;;
--sim|--simulator) BUILD_SIMULATOR=true ;;
--no-sim|--no-simulator) BUILD_SIMULATOR=false ;;
--iphone|--device) BUILD_IPHONE=true ;;
--no-iphone|--no-device) BUILD_IPHONE=false ;;
*) ;;
esac
done

DIST="$PWD/dist"
mkdir -p $DIST

mkdir -p $DIST/intermediates

checkpoint "Cleanup NativeScriptDefaultMetadata"
xcodebuild -project v8ios.xcodeproj \
-target NativeScriptDefaultMetadata \
-configuration Release clean \
-quiet

if $BUILD_SIMULATOR; then
checkpoint "Building NativeScriptDefaultMetadata for iphone simulators (multi-arch)"
xcodebuild archive -project v8ios.xcodeproj \
-scheme NativeScriptDefaultMetadata \
-configuration Release \
-destination "generic/platform=iOS Simulator" \
-quiet \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
-archivePath $DIST/intermediates/NativeScriptDefaultMetadata.iphonesimulator.xcarchive
fi

if $BUILD_IPHONE; then
checkpoint "Building NativeScriptDefaultMetadata for ARM64 device"
xcodebuild archive -project v8ios.xcodeproj \
-scheme NativeScriptDefaultMetadata \
-configuration Release \
-destination "generic/platform=iOS" \
-quiet \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
-archivePath $DIST/intermediates/NativeScriptDefaultMetadata.iphoneos.xcarchive
fi

if $BUILD_CATALYST; then
checkpoint "Building NativeScriptDefaultMetadata for Mac Catalyst"
xcodebuild archive -project v8ios.xcodeproj \
-scheme NativeScriptDefaultMetadata \
-configuration Release \
-destination "generic/platform=macOS,variant=Mac Catalyst" \
-quiet \
EXCLUDED_ARCHS="x86_64" \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
-archivePath $DIST/intermediates/NativeScriptDefaultMetadata.maccatalyst.xcarchive
fi

XCFRAMEWORKS=()
if $BUILD_CATALYST; then
XCFRAMEWORKS+=( -framework "$DIST/intermediates/NativeScriptDefaultMetadata.maccatalyst.xcarchive/Products/Library/Frameworks/NativeScriptDefaultMetadata.framework" \
-debug-symbols "$DIST/intermediates/NativeScriptDefaultMetadata.maccatalyst.xcarchive/dSYMs/NativeScriptDefaultMetadata.framework.dSYM" )
fi

if $BUILD_SIMULATOR; then
XCFRAMEWORKS+=( -framework "$DIST/intermediates/NativeScriptDefaultMetadata.iphonesimulator.xcarchive/Products/Library/Frameworks/NativeScriptDefaultMetadata.framework" \
-debug-symbols "$DIST/intermediates/NativeScriptDefaultMetadata.iphonesimulator.xcarchive/dSYMs/NativeScriptDefaultMetadata.framework.dSYM" )
fi

if $BUILD_IPHONE; then
XCFRAMEWORKS+=( -framework "$DIST/intermediates/NativeScriptDefaultMetadata.iphoneos.xcarchive/Products/Library/Frameworks/NativeScriptDefaultMetadata.framework" \
-debug-symbols "$DIST/intermediates/NativeScriptDefaultMetadata.iphoneos.xcarchive/dSYMs/NativeScriptDefaultMetadata.framework.dSYM" )
fi

checkpoint "Creating NativeScriptDefaultMetadata.xcframework"
OUTPUT_DIR="$DIST/NativeScriptDefaultMetadata.xcframework"
rm -rf $OUTPUT_DIR
xcodebuild -create-xcframework ${XCFRAMEWORKS[@]} -output "$OUTPUT_DIR"

rm -rf "$DIST/intermediates"
22 changes: 10 additions & 12 deletions build_nativescript.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,22 @@ 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 ;;
* )
# case patterns are globs, not regexes: whatever gets past this arm is all digits
''|*[!0-9]*)
if [ -n "$arg" ]; then
echo "warning: invalid boolean argument ('$arg'). Expected true or false" >&2
fi
echo false
;;
* )
if [ "$arg" -eq 0 ]; then
echo false
else
echo true
fi
;;
esac;
}

Expand Down Expand Up @@ -54,7 +55,7 @@ if ! $VERBOSE; then
fi

DEV_TEAM=${DEVELOPMENT_TEAM:-}
DIST=$(PWD)/dist
DIST="$PWD/dist"
mkdir -p $DIST

mkdir -p $DIST/intermediates
Expand All @@ -75,7 +76,6 @@ xcodebuild archive -project v8ios.xcodeproj \
EXCLUDED_ARCHS="x86_64" \
SKIP_INSTALL=NO \
BUILD_LIBRARIES_FOR_DISTRIBUTION=YES \
INCLUDE_DEFAULT_METADATA=$INCLUDE_DEFAULT_METADATA\
-archivePath $DIST/intermediates/NativeScript.maccatalyst.xcarchive
fi

Expand All @@ -90,7 +90,6 @@ xcodebuild archive -project v8ios.xcodeproj \
DEVELOPMENT_TEAM=$DEV_TEAM \
SKIP_INSTALL=NO \
BUILD_LIBRARIES_FOR_DISTRIBUTION=YES \
INCLUDE_DEFAULT_METADATA=$INCLUDE_DEFAULT_METADATA\
-archivePath $DIST/intermediates/NativeScript.iphonesimulator.xcarchive
fi

Expand All @@ -105,7 +104,6 @@ xcodebuild archive -project v8ios.xcodeproj \
DEVELOPMENT_TEAM=$DEV_TEAM \
SKIP_INSTALL=NO \
BUILD_LIBRARIES_FOR_DISTRIBUTION=YES \
INCLUDE_DEFAULT_METADATA=$INCLUDE_DEFAULT_METADATA\
-archivePath $DIST/intermediates/NativeScript.iphoneos.xcarchive
fi

Expand Down
10 changes: 10 additions & 0 deletions build_spm_artifacts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,22 @@ zip_xcframework "TKLiveSync.xcframework" "$TK_ZIP"
NS_SUM="$(compute_checksum "$OUT/$NS_ZIP")"
TK_SUM="$(compute_checksum "$OUT/$TK_ZIP")"

# Default metadata ships for the iOS family only.
if [ "$TARGET" = "ios" ]; then
DM_ZIP="NativeScriptDefaultMetadata.xcframework.zip"
zip_xcframework "NativeScriptDefaultMetadata.xcframework" "$DM_ZIP"
DM_SUM="$(compute_checksum "$OUT/$DM_ZIP")"
fi

# Per-target filename so the iOS and visionOS env files don't collide when the
# release/stamp jobs merge both platforms' artifacts into one directory.
CHECKSUMS="$OUT/checksums-$TARGET.env"
{
echo "${NS_KEY}=${NS_SUM}"
echo "${TK_KEY}=${TK_SUM}"
if [ -n "${DM_SUM:-}" ]; then
echo "NS_CHECKSUM_DEFAULTMETADATA_IOS=${DM_SUM}"
fi
} > "$CHECKSUMS"

checkpoint "SwiftPM artifacts ready in $OUT:"
Expand Down
Loading
Loading