diff --git a/.gitignore b/.gitignore index 5003298477..ae49b9d96e 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,15 @@ tsconfig.tsbuildinfo # Claude Code local settings .claude/settings.local.json + +# React Native SwiftPM +# Build-time symlink to Xcode's generated Swift interop headers +.spm-derived-headers +.build/ +.swiftpm/ +**/*.xcodeproj/.spm-injected.json +# Public-header trees staged (as symlinks) by the autolinking plugin at +# `react-native spm add` time — machine-generated, never committed +**/nitrogen/generated/include/ +**/nitrogen/generated/include-bridge/ +packages/react-native-nitro-modules/include/ diff --git a/example/ios/NitroExample.xcodeproj/project.pbxproj b/example/ios/NitroExample.xcodeproj/project.pbxproj index 12576cfbcb..113c3ed9e9 100644 --- a/example/ios/NitroExample.xcodeproj/project.pbxproj +++ b/example/ios/NitroExample.xcodeproj/project.pbxproj @@ -532,7 +532,8 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; + PODFILE_DIR = "$(SRCROOT)"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_ENABLE_EXPLICIT_MODULES = NO; @@ -615,7 +616,8 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; + PODFILE_DIR = "$(SRCROOT)"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ENABLE_EXPLICIT_MODULES = NO; SWIFT_OBJC_INTEROP_MODE = objcxx; diff --git a/example/package.json b/example/package.json index 0f702c91b3..f01a767a62 100644 --- a/example/package.json +++ b/example/package.json @@ -24,24 +24,24 @@ "@react-navigation/native": "^7.2.5", "deep-equal": "^2.2.3", "react": "19.2.3", - "react-native": "0.85.3", + "react-native": "0.87.0", "react-native-nitro-test": "*", "react-native-nitro-test-external": "*", "react-native-nitro-modules": "*", "react-native-safe-area-context": "^5.8.0", - "react-native-screens": "^4.25.2" + "react-native-screens": "^4.27.0" }, "devDependencies": { "@babel/core": "^7.29.7", "@babel/preset-env": "^7.29.7", "@babel/runtime": "^7.29.7", - "@react-native-community/cli": "20.1.3", - "@react-native-community/cli-platform-android": "20.1.3", - "@react-native-community/cli-platform-ios": "20.1.3", - "@react-native/babel-preset": "0.85.3", - "@react-native/eslint-config": "0.85.3", - "@react-native/metro-config": "0.85.3", - "@react-native/typescript-config": "0.85.3", + "@react-native-community/cli": "20.2.0", + "@react-native-community/cli-platform-android": "20.2.0", + "@react-native-community/cli-platform-ios": "20.2.0", + "@react-native/babel-preset": "0.87.0", + "@react-native/eslint-config": "0.87.0", + "@react-native/metro-config": "0.87.0", + "@react-native/typescript-config": "0.87.0", "@react-native-harness/platform-android": "1.4.1", "@react-native-harness/platform-apple": "1.4.1", "@types/deep-equal": "^1.0.4", diff --git a/example/src/screens/HybridObjectTestsScreen.tsx b/example/src/screens/HybridObjectTestsScreen.tsx index efe067fd64..e310bead93 100644 --- a/example/src/screens/HybridObjectTestsScreen.tsx +++ b/example/src/screens/HybridObjectTestsScreen.tsx @@ -17,13 +17,22 @@ import { } from 'react-native-nitro-test' import { getTests, type TestRunner } from '../getTests' import { logPrototypeChain } from '../logPrototypeChain' -import SegmentedControl from '@react-native-segmented-control/segmented-control' +import SegmentedControlNative, { + type SegmentedControlProps, +} from '@react-native-segmented-control/segmented-control' import { NitroModules } from 'react-native-nitro-modules' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useColors } from '../useColors' import { TestCase, TestState } from '../components/TestCase' import { KeyboardDismissBackground } from '../components/KeyboardDismissBackground' +// react-native 0.87 dropped `NativeMethods` from its public types, which breaks +// this (unmaintained) package's `Constructor & typeof Component` +// intersection — the resulting class loses its `props`. Its own props type is +// unaffected, so re-type the default export as a plain component. +const SegmentedControl = + SegmentedControlNative as unknown as React.ComponentType + logPrototypeChain(HybridChild) console.log(HybridBase.baseValue) console.log(HybridChild.baseValue) diff --git a/package.json b/package.json index 90750efeb6..de2d3fc748 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ "@eslint/eslintrc": "^3.3.5", "@eslint/js": "^9.39.4", "@jamesacarr/eslint-formatter-github-actions": "^0.2.0", - "@react-native/eslint-config": "0.85.3", - "@react-native/jest-preset": "0.85.3", + "@react-native/eslint-config": "0.87.0", + "@react-native/jest-preset": "0.87.0", "@release-it-plugins/workspaces": "^5.0.3", "@release-it/bumper": "^7.0.5", "@release-it/conventional-changelog": "^10.0.6", @@ -53,7 +53,7 @@ "jest": "^29.7.0", "prettier": "^3.8.3", "react": "19.2.3", - "react-native": "0.85.3", + "react-native": "0.87.0", "release-it": "^19.2.4", "typescript": "~6.0.3", "typescript-eslint": "^8.60.0" diff --git a/packages/nitrogen/src/autolinking/createIOSAutolinking.ts b/packages/nitrogen/src/autolinking/createIOSAutolinking.ts index 0dbffcc0d6..695326cb9a 100644 --- a/packages/nitrogen/src/autolinking/createIOSAutolinking.ts +++ b/packages/nitrogen/src/autolinking/createIOSAutolinking.ts @@ -1,16 +1,57 @@ +import fs from 'fs' +import path from 'path' import type { Autolinking } from './Autolinking.js' import { createHybridObjectIntializer } from './ios/createHybridObjectInitializer.js' +import { createPackageSwift } from './ios/createPackageSwift.js' import { createPodspecRubyExtension } from './ios/createPodspecRubyExtension.js' import { createSwiftCxxBridge } from './ios/createSwiftCxxBridge.js' import { createSwiftUmbrellaHeader } from './ios/createSwiftUmbrellaHeader.js' +import { getSpmPackageName } from './ios/getSpmPackageName.js' interface IOSAutolinking extends Autolinking {} -export function createIOSAutolinking(): IOSAutolinking { +/** + * Deletes a previously generated `Package.swift` when one can no longer be + * generated (e.g. the package has no readable `package.json`). + * + * Nitrogen's own dangling-file cleanup only covers `nitrogen/generated/`, and + * `Package.swift` lives at the PACKAGE ROOT — so without this it would linger + * and keep React Native's SwiftPM autolinking treating the module as a + * self-managed package. + * + * Only removes files carrying nitrogen's generated-by marker: a hand-authored + * `Package.swift` (a library shipping its own SwiftPM support) is never + * touched. + */ +function removeGeneratedPackageSwift(): void { + const manifest = path.join(process.cwd(), 'Package.swift') + try { + if (!fs.existsSync(manifest)) return + const content = fs.readFileSync(manifest, 'utf8') + if (!content.includes('This file was generated by nitrogen')) return + fs.rmSync(manifest) + } catch { + // Best-effort: a stale manifest is not worth failing codegen over. + } +} + +export function createIOSAutolinking(outputDirectory: string): IOSAutolinking { const podspecExtension = createPodspecRubyExtension() const swiftCxxBridge = createSwiftCxxBridge() const swiftUmbrellaHeader = createSwiftUmbrellaHeader() const hybridObjectInitializer = createHybridObjectIntializer() + // React Native SwiftPM (Preview) support. Purely additive — CocoaPods apps + // never read Package.swift — and needs no configuration: the package + // identity is derived from the npm package name exactly as React Native's + // autolinker derives it. + const spmPackageName = getSpmPackageName(process.cwd()) + const packageSwift = + spmPackageName != null + ? createPackageSwift(spmPackageName, outputDirectory) + : [] + // Also covers the case where a manifest cannot be expressed for this output + // directory - see `createPackageSwift`. + if (packageSwift.length === 0) removeGeneratedPackageSwift() return { platform: 'ios', @@ -19,6 +60,7 @@ export function createIOSAutolinking(): IOSAutolinking { ...swiftCxxBridge, swiftUmbrellaHeader, ...hybridObjectInitializer, + ...packageSwift, ], } } diff --git a/packages/nitrogen/src/autolinking/ios/createPackageSwift.ts b/packages/nitrogen/src/autolinking/ios/createPackageSwift.ts new file mode 100644 index 0000000000..b68279df9e --- /dev/null +++ b/packages/nitrogen/src/autolinking/ios/createPackageSwift.ts @@ -0,0 +1,355 @@ +import fs from 'fs' +import path from 'path' +import { NitroConfig } from '../../config/NitroConfig.js' +import { getAllKnownTypes } from '../../syntax/createType.js' +import { + createFileMetadataString, + isNotDuplicate, +} from '../../syntax/helpers.js' +import type { SourceFile } from '../../syntax/SourceFile.js' +import { getTypeAs } from '../../syntax/types/getTypeAs.js' +import { HybridObjectType } from '../../syntax/types/HybridObjectType.js' +import { getSpmPackageName } from './getSpmPackageName.js' +import { Logger } from '../../Logger.js' + +type AutolinkingFile = Omit & { + language: 'swift' | 'c++' +} + +/** Splits a path into its segments, dropping empty ones (`''`, `'.'`). */ +function toSegments(relativePath: string): string[] { + return relativePath.split(path.sep).filter((s) => s !== '' && s !== '.') +} + +/** + * Generates React Native SwiftPM (Preview) support for a Nitro Module: + * + * - `Package.swift` at the package root — a "self-managed" manifest for RN's + * SwiftPM autolinking. Because SwiftPM cannot compile Swift and C++ in one + * target (CocoaPods can), the module is split into an acyclic 3-target + * chain: `Core` (C++ specs + user C++) ← `` (Swift, C++ interop) + * ← `SwiftBridge` (every C++/ObjC++ TU that calls INTO Swift via the + * Swift-generated `-Swift.h`). The same three phases exist under + * CocoaPods inside its one mixed target (Swift compiles first, emits + * `-Swift.h`, then the ObjC++/C++ TUs compile against it) — SwiftPM + * just requires each phase to be its own target. + * - `nitrogen/generated/include//` — a flat tree of SYMLINKS to this + * module's public headers, giving SwiftPM the single `publicHeadersPath` + * directory it requires. The headers themselves are NOT moved, so the + * podspec, the Android CMake include dirs and every existing `#include` + * spelling stay exactly as they were. + * - `nitrogen/generated/include-bridge/` — placeholder public-headers dir for + * the SwiftBridge target (each clang target needs its own disjoint one). + * + * This is purely ADDITIVE: CocoaPods consumption via `+autolinking.rb` + * is unchanged. RN's SwiftPM autolinking prefers the Package.swift when the + * app uses `react-native spm`; CocoaPods apps never read it. + * + * Conventions (validated against react-native-nitro-test): + * - The dep's react-native.config.js sets `spm.name` = iosModuleName and + * `spm.dependencies` = its native npm siblings. + * - User Swift code lives in `ios/`, user C++ in `cpp/` (both optional). + * Swift files go to the Swift target; user C-family files go to the glue + * target (they may need `-Swift.h`). + * - `-Swift.h` is consumed TEXTUALLY: under `swift build` SwiftPM vends + * it natively; under Xcode, RN's SPM sync symlinks Xcode's generated-header + * dir to `.spm-derived-headers/` inside the package. + */ +export function createPackageSwift( + spmPackageName: string, + outputDirectory: string +): AutolinkingFile[] { + const moduleName = NitroConfig.current.getIosModuleName() + const packageRoot = process.cwd() + + // `--out` is user-configurable, so neither the depth of the output directory + // nor its name may be assumed here (the default is `nitrogen/generated`). + // - `packageRootSubdirectory` walks back from `/ios/` (where + // autolinking files are written) to the package root, where SwiftPM + // requires the manifest to live. + // - `generatedDir` is the same output directory as SwiftPM sees it: + // relative to the package root, always POSIX-separated. + const generatedDirAbs = path.resolve(packageRoot, outputDirectory) + const packageRootSubdirectory = toSegments( + path.relative(path.join(generatedDirAbs, 'ios'), packageRoot) + ) + const generatedDirSegments = toSegments( + path.relative(packageRoot, generatedDirAbs) + ) + const generatedDir = generatedDirSegments.join('/') + if (generatedDirSegments.includes('..') || generatedDir === '') { + // SwiftPM targets cannot reference sources outside the package, so a + // manifest could only be written if it were broken. Skip it — CocoaPods + // autolinking (the default) is unaffected. + Logger.warn( + `⚠️ Skipping Package.swift for ${moduleName}: the output directory ` + + `("${outputDirectory}") is not inside the package root. React Native ` + + `SwiftPM support requires nitrogen's output to live inside the package.` + ) + return [] + } + if (generatedDir !== 'nitrogen/generated') { + // The consumer-side plugin (rn-spm-autolinking-plugin.cjs) stages public + // headers from the hardcoded `nitrogen/generated/` layout. + Logger.warn( + `⚠️ ${moduleName} generates into "${generatedDir}" instead of ` + + `"nitrogen/generated" — React Native SwiftPM autolinking will not ` + + `find this module's generated headers.` + ) + } + + const types = getAllKnownTypes('swift') + const siblingModules = types + .filter((t) => t.kind === 'hybrid-object') + .map((t) => getTypeAs(t, HybridObjectType).sourceConfig) + .filter((config) => config.getIosModuleName() !== moduleName) + .map((config) => getSpmPackageName(config.getPackageRoot())) + .filter((name) => name != null) + .filter(isNotDuplicate) + .sort() + + const listFiles = (dir: string, exts: string[]): string[] => { + const abs = path.join(packageRoot, dir) + if (!fs.existsSync(abs)) return [] + const out: string[] = [] + const walk = (rel: string) => { + for (const entry of fs.readdirSync(path.join(packageRoot, rel), { + withFileTypes: true, + })) { + const relPath = `${rel}/${entry.name}` + if (entry.isDirectory()) walk(relPath) + else if (exts.some((e) => entry.name.endsWith(e))) out.push(relPath) + } + } + walk(dir) + return out.sort() + } + const userSwiftSources = listFiles('ios', ['.swift']) + const userClangSources = [ + ...listFiles('ios', ['.c', '.cpp', '.cc', '.m', '.mm']), + ...listFiles('cpp', ['.c', '.cpp', '.cc', '.m', '.mm']), + ] + const hasUserCpp = fs.existsSync(path.join(packageRoot, 'cpp')) + + const hasSwiftPart = fs.existsSync(path.join(generatedDirAbs, 'ios/swift')) + const hasGeneratedIosCpp = fs.existsSync( + path.join(generatedDirAbs, 'ios/c++') + ) + + const quoteList = (items: string[], indentation: string): string => + items.map((s) => `${indentation}"${s}",`).join('\n') + + const siblingPackageDeps = siblingModules + .map((m) => ` .package(name: "${m}", path: "../${m}"),`) + .join('\n') + const siblingProductDeps = (indentation: string): string => + siblingModules + .map((m) => `${indentation}.product(name: "${m}", package: "${m}"),`) + .join('\n') + + const packageSwift = `// swift-tools-version: 6.0 +${createFileMetadataString('Package.swift')} +// +// React Native SwiftPM manifest for ${moduleName}. Consumed by RN's +// SwiftPM autolinking as a "self-managed" package through the +// /ios/build/generated/autolinking/libs/${moduleName}/ symlink — the +// relative package paths below resolve against that symlink location: +// ../../../../xcframeworks -> /ios/build/xcframeworks (ReactNative) +// ../NitroModules -> libs/NitroModules (nitro core) +// +// NOTE: these path references assume React Native SwiftPM's default +// local-artifacts mode (xcframeworks downloaded into /ios/build/). +// RN's experimental remote-package mode (RN_SPM_REMOTE_URL) is not yet +// supported by generated Nitro manifests. +// + +import PackageDescription + +let reactCxxDefines: [CXXSetting] = [ + // Match the prebuilt React.framework's config-gated C++ ABI. + .define("DEBUG", .when(configuration: .debug)), + .define("NDEBUG", .when(configuration: .release)), + // React Native's prebuilt SwiftPM distribution is New Architecture only. + .define("RCT_NEW_ARCH_ENABLED", to: "1"), +] + +let headerDirs: [CXXSetting] = [ +${hasUserCpp ? ' .headerSearchPath("cpp"),\n' : ''} .headerSearchPath("${generatedDir}/shared/c++"), + .headerSearchPath("${generatedDir}/shared/c++/views"), + .headerSearchPath("${generatedDir}/ios"), + .headerSearchPath("${generatedDir}/ios/c++"), + .headerSearchPath("${generatedDir}/ios/c++/views"), +] + +let package = Package( + name: "${spmPackageName}", + platforms: [.iOS(.v15), .visionOS(.v1)], + products: [${ + hasSwiftPart + ? ` + // Only the root of the target chain is vended - SwiftPM pulls + // ${moduleName} (Swift) and ${moduleName}Core (C++) in as its target + // dependencies, propagating their module, public headers and objects + // to dependents. Naming them here would add nothing.` + : '' + } + .library(name: "${spmPackageName}", targets: ["${moduleName}${ + hasSwiftPart ? 'SwiftBridge' : 'Core' + }"]), + ], + dependencies: [ + .package(name: "ReactNative", path: "../../../../xcframeworks"), + .package(name: "NitroModules", path: "../NitroModules"), +${siblingPackageDeps} + ], + targets: [ + // C++ layer: nitrogen's cross-platform specs + the user's C++ hybrids.${ + hasSwiftPart + ? ` + // No Swift dependency — this is what the Swift target imports.` + : ` + // This module has no Swift HybridObjects, so the ObjC++ registration + // compiles here too (SwiftPM only forbids mixing SWIFT with C-family + // sources) and no Swift/SwiftBridge targets are needed.` + } + .target( + name: "${moduleName}Core", + dependencies: [ + .product(name: "NitroModules", package: "NitroModules"), +${siblingProductDeps(' ')} + .product(name: "ReactHeaders", package: "ReactNative"), + .product(name: "ReactNativeHeaders", package: "ReactNative"), + .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), + ], + path: ".", + sources: [${ + hasSwiftPart + ? ` + "${generatedDir}/shared/c++",` + : ` +${quoteList(userClangSources, ' ')} + "${generatedDir}/shared/c++",${ + hasGeneratedIosCpp + ? ` + "${generatedDir}/ios/c++",` + : '' + } + "${generatedDir}/ios/${moduleName}Autolinking.mm",` + } + ], + publicHeadersPath: "${generatedDir}/include", + cxxSettings: headerDirs + reactCxxDefines${ + hasSwiftPart + ? '' + : `, + linkerSettings: [ + .linkedFramework("Foundation"), + .linkedFramework("UIKit", .when(platforms: [.iOS])), + ]` + } + ),${ + !hasSwiftPart + ? '' + : ` + // Swift layer: nitrogen's Swift specs/_cxx bridges + user Swift hybrids. + .target( + name: "${moduleName}", + dependencies: [ + "${moduleName}Core", + .product(name: "NitroModules", package: "NitroModules"), +${siblingProductDeps(' ')} + ], + path: ".", + sources: [ +${quoteList(userSwiftSources, ' ')} + "${generatedDir}/ios/swift", + "${generatedDir}/ios/${moduleName}Autolinking.swift", + ], + swiftSettings: [ + .interoperabilityMode(.Cxx), + .swiftLanguageMode(.v5), + ] + ), + // SwiftBridge layer: every C++/ObjC++ TU that consumes the Swift-generated + // interface header (${moduleName}-Swift.h). Structurally required: + // these TUs cannot live in ${moduleName}Core (Core -> Swift -> Core + // cycle) nor in the Swift target (SwiftPM forbids mixed languages). + .target( + name: "${moduleName}SwiftBridge", + dependencies: [ + "${moduleName}", + "${moduleName}Core", + .product(name: "NitroModules", package: "NitroModules"), +${siblingProductDeps(' ')} + .product(name: "ReactHeaders", package: "ReactNative"), + .product(name: "ReactNativeHeaders", package: "ReactNative"), + .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), + ], + path: ".", + sources: [ +${quoteList(userClangSources, ' ')} + "${generatedDir}/ios/c++", + "${generatedDir}/ios/${moduleName}-Swift-Cxx-Bridge.cpp", + "${generatedDir}/ios/${moduleName}Autolinking.mm", + ], + publicHeadersPath: "${generatedDir}/include-bridge", + cxxSettings: headerDirs + reactCxxDefines + [ + // Under Xcode, React Native's SPM sync symlinks + // $OBJROOT/GeneratedModuleMaps- (where Xcode stages + // the Swift-generated ${moduleName}-Swift.h) to this stable + // path, so the Swift-Cxx umbrella can include it TEXTUALLY. + // Under \`swift build\` SwiftPM vends the header natively and + // this path simply doesn't exist. + .headerSearchPath(".spm-derived-headers"), + ], + linkerSettings: [ + .linkedFramework("Foundation"), + .linkedFramework("UIKit", .when(platforms: [.iOS])), + ] + ),` + } + ], + cxxLanguageStandard: .cxx20 +) +` + + const files: AutolinkingFile[] = [ + { + content: packageSwift, + name: 'Package.swift', + subdirectory: packageRootSubdirectory, + platform: 'ios', + language: 'swift', + }, + ] + if (hasSwiftPart) { + files.push({ + content: `${createFileMetadataString(`${moduleName}Exports.swift`)} + +// Re-exports this module's C++ half (the ${moduleName}Core clang target) so +// that every OTHER Swift file in this module sees nitrogen's generated C++ +// specs WITHOUT importing it itself — \`@_exported import\` is visible module- +// wide, not just in this file. +// +// Under CocoaPods the module is a single mixed target, so no ${moduleName}Core +// module exists and this file compiles to nothing. +#if canImport(${moduleName}Core) +@_exported import ${moduleName}Core +#endif +`, + name: `${moduleName}Exports.swift`, + subdirectory: ['swift'], + platform: 'ios', + language: 'swift', + }) + } + // The `publicHeadersPath` directories referenced above + // (/include// and include-bridge/) do not exist in + // the source tree at all: Nitro's RN SwiftPM autolinking plugin creates + // them — and stages the public headers into include// as a flat tree + // of symlinks — when the app runs `react-native spm add`/`update`. That is + // the SwiftPM analogue of CocoaPods staging `Pods/Headers/Public//` + // at `pod install` time, and it always precedes a build (SwiftPM validates + // the directories at build time, not manifest-parse time). + return files +} diff --git a/packages/nitrogen/src/autolinking/ios/createSwiftUmbrellaHeader.ts b/packages/nitrogen/src/autolinking/ios/createSwiftUmbrellaHeader.ts index 71e5d0dfe3..641ffbb1b0 100644 --- a/packages/nitrogen/src/autolinking/ios/createSwiftUmbrellaHeader.ts +++ b/packages/nitrogen/src/autolinking/ios/createSwiftUmbrellaHeader.ts @@ -76,8 +76,24 @@ ${swiftForwardDeclares.sort().join('\n')} #elif __has_include(<${moduleName}/${moduleName}-Swift.h>) #include <${moduleName}/${moduleName}-Swift.h> #else +// React Native SwiftPM (Preview): Xcode stages the generated header in +// DerivedData ($OBJROOT/GeneratedModuleMaps-/). Nitro's RN SwiftPM +// autolinking plugin symlinks $OBJROOT to \`.spm-derived-headers\` inside this +// package (a headerSearchPath of the glue target), and TargetConditionals +// picks the platform subdirectory. +#include +#if TARGET_OS_VISION && TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-xrsimulator/${moduleName}-Swift.h") +#include "GeneratedModuleMaps-xrsimulator/${moduleName}-Swift.h" +#elif TARGET_OS_VISION && __has_include("GeneratedModuleMaps-xros/${moduleName}-Swift.h") +#include "GeneratedModuleMaps-xros/${moduleName}-Swift.h" +#elif TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-iphonesimulator/${moduleName}-Swift.h") +#include "GeneratedModuleMaps-iphonesimulator/${moduleName}-Swift.h" +#elif !TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-iphoneos/${moduleName}-Swift.h") +#include "GeneratedModuleMaps-iphoneos/${moduleName}-Swift.h" +#else #error ${moduleName}'s autogenerated Swift header cannot be found! Make sure the Swift module's name (= podspec name) is actually "${moduleName}", and try building the app first. #endif +#endif ` return { diff --git a/packages/nitrogen/src/autolinking/ios/getSpmPackageName.ts b/packages/nitrogen/src/autolinking/ios/getSpmPackageName.ts new file mode 100644 index 0000000000..61694d4c64 --- /dev/null +++ b/packages/nitrogen/src/autolinking/ios/getSpmPackageName.ts @@ -0,0 +1,68 @@ +import fs from 'fs' +import { createRequire } from 'module' +import path from 'path' + +const require = createRequire(import.meta.url) + +/** + * Mirrors React Native's `toSwiftName()` (scripts/spm/spm-utils.js), which is + * how its SwiftPM autolinker names a dependency's package and product when the + * library does not override it. + * + * @example `react-native-nitro-test` -> `ReactNativeNitroTest` + * @example `@margelo/nitro-image` -> `NitroImage` + */ +function toSwiftName(npmName: string): string { + return npmName + .replace(/^@[^/]+\//, '') + .split(/[^a-zA-Z0-9]+/) + .filter(Boolean) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join('') +} + +/** + * The SwiftPM package/product name React Native's autolinker will resolve this + * module by. + * + * This is NOT the module's Swift module name (that stays `iosModuleName`) — it + * is the identity of the *package*, which must match what React Native expects + * or resolution fails with + * + * product 'ReactNativeNitroTest' required by package 'autolinking' ... + * not found in package 'ReactNativeNitroTest' + * + * React Native derives it from the npm package name, so nitrogen derives it the + * same way — meaning a module needs no configuration at all to be consumable + * via SwiftPM. A library that wants a different identity can still override it + * with `spm.name` in its `react-native.config.js` (React Native's documented + * escape hatch), and that override is honoured here. + */ +export function getSpmPackageName(packageRoot: string): string | undefined { + for (const file of ['react-native.config.js', 'react-native.config.cjs']) { + const configPath = path.join(packageRoot, file) + if (!fs.existsSync(configPath)) continue + try { + const config: unknown = require(configPath) + const name = (config as { spm?: { name?: unknown } } | undefined)?.spm + ?.name + if (typeof name === 'string' && name.length > 0) return name + } catch { + // Unreadable config — fall through to the derived name. + } + break + } + + try { + const packageJson: unknown = JSON.parse( + fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8') + ) + const npmName = (packageJson as { name?: unknown } | undefined)?.name + if (typeof npmName === 'string' && npmName.length > 0) { + return toSwiftName(npmName) + } + } catch { + // No readable package.json — cannot determine an identity. + } + return undefined +} diff --git a/packages/nitrogen/src/config/NitroConfig.ts b/packages/nitrogen/src/config/NitroConfig.ts index 658d2eee2e..7d8671b09d 100644 --- a/packages/nitrogen/src/config/NitroConfig.ts +++ b/packages/nitrogen/src/config/NitroConfig.ts @@ -15,10 +15,20 @@ const ANDROID_BASE_NAMESPACE = ['com', 'margelo', 'nitro'] */ export class NitroConfig { private readonly config: NitroUserConfig + private readonly packageRoot: string private static singleton: NitroConfig | undefined - constructor(config: NitroUserConfig) { + constructor(config: NitroUserConfig, packageRoot: string = process.cwd()) { this.config = config + this.packageRoot = packageRoot + } + + /** + * The directory this config's `nitro.json` lives in — i.e. the module's + * package root. Used to resolve sibling modules' `package.json`. + */ + getPackageRoot(): string { + return this.packageRoot } static get current(): NitroConfig { diff --git a/packages/nitrogen/src/nitrogen.ts b/packages/nitrogen/src/nitrogen.ts index 3fe66d57a6..7155d28b46 100644 --- a/packages/nitrogen/src/nitrogen.ts +++ b/packages/nitrogen/src/nitrogen.ts @@ -198,7 +198,7 @@ export async function runNitrogen({ const autolinkingFiles: Autolinking[] = [] if (usedPlatforms.includes('ios')) { - autolinkingFiles.push(createIOSAutolinking()) + autolinkingFiles.push(createIOSAutolinking(outputDirectory)) } if (usedPlatforms.includes('android')) { autolinkingFiles.push(createAndroidAutolinking(writtenFiles)) diff --git a/packages/nitrogen/src/syntax/c++/CppStruct.ts b/packages/nitrogen/src/syntax/c++/CppStruct.ts index 586066827f..6113423d02 100644 --- a/packages/nitrogen/src/syntax/c++/CppStruct.ts +++ b/packages/nitrogen/src/syntax/c++/CppStruct.ts @@ -1,4 +1,4 @@ -import type { FileWithReferencedTypes } from '../SourceFile.js' +import type { FileWithReferencedTypes, SourceFile } from '../SourceFile.js' import { indent } from '../../utils.js' import { createFileMetadataString, isNotDuplicate } from '../helpers.js' import type { NamedType } from '../types/Type.js' @@ -55,7 +55,9 @@ export function createCppStruct( let equatableFunc: string const isEquatable = properties.every((p) => p.isEquatable) if (isEquatable) { - equatableFunc = `friend bool operator==(const ${typename}& lhs, const ${typename}& rhs) = default;` + // Declared here, defaulted OUT-OF-LINE (C++20) in ${typename}.cpp — see + // createCppStructEqualityDefinition for why it cannot stay in-class. + equatableFunc = `friend bool operator==(const ${typename}& lhs, const ${typename}& rhs);` } else { const nonEquatableTypes = properties .filter((p) => !p.isEquatable) @@ -147,3 +149,32 @@ namespace margelo::nitro { platform: 'shared', } } + +/** + * Creates the companion `.cpp` for an equatable struct, holding the + * out-of-line defaulted `operator==` (C++20). + */ +export function createCppStructEqualityDefinition( + typename: string +): SourceFile { + const cxxNamespace = NitroConfig.current.getCxxNamespace('c++') + const cppCode = ` +${createFileMetadataString(`${typename}.cpp`)} + +#include "${typename}.hpp" + +namespace ${cxxNamespace} { + + // Out-of-line defaulted comparison (C++20) — see ${typename}.hpp. + bool operator==(const ${typename}& lhs, const ${typename}& rhs) = default; + +} // namespace ${cxxNamespace} + ` + return { + content: cppCode, + name: `${typename}.cpp`, + subdirectory: [], + language: 'c++', + platform: 'shared', + } +} diff --git a/packages/nitrogen/src/syntax/types/StructType.ts b/packages/nitrogen/src/syntax/types/StructType.ts index 23845763a8..f22e587e4c 100644 --- a/packages/nitrogen/src/syntax/types/StructType.ts +++ b/packages/nitrogen/src/syntax/types/StructType.ts @@ -1,6 +1,9 @@ import { NitroConfig } from '../../config/NitroConfig.js' import type { Language } from '../../getPlatformSpecs.js' -import { createCppStruct } from '../c++/CppStruct.js' +import { + createCppStruct, + createCppStructEqualityDefinition, +} from '../c++/CppStruct.js' import { getForwardDeclaration } from '../c++/getForwardDeclaration.js' import { type FileWithReferencedTypes, @@ -13,11 +16,15 @@ export class StructType implements Type { readonly structName: string readonly properties: NamedType[] readonly declarationFile: FileWithReferencedTypes + readonly equalityDefinitionFile: SourceFile | undefined constructor(structName: string, properties: NamedType[]) { this.structName = structName this.properties = properties this.declarationFile = createCppStruct(structName, properties) + this.equalityDefinitionFile = properties.every((p) => p.isEquatable) + ? createCppStructEqualityDefinition(structName) + : undefined if (this.structName.startsWith('__')) { throw new Error( @@ -64,7 +71,11 @@ export class StructType implements Type { const referencedTypes = this.declarationFile.referencedTypes.flatMap((r) => r.getExtraFiles() ) - return [this.declarationFile, ...referencedTypes] + const files: SourceFile[] = [this.declarationFile, ...referencedTypes] + if (this.equalityDefinitionFile != null) { + files.push(this.equalityDefinitionFile) + } + return files } getRequiredImports(language: Language): SourceImport[] { const imports: SourceImport[] = [] diff --git a/packages/nitrogen/src/utils.ts b/packages/nitrogen/src/utils.ts index d841be3743..85f78d16b9 100644 --- a/packages/nitrogen/src/utils.ts +++ b/packages/nitrogen/src/utils.ts @@ -154,7 +154,7 @@ export function getHybridObjectNitroModuleConfig( const hasNitroJson = fs.existsSync(nitroJsonPath) if (hasNitroJson) { const config = readUserConfig(nitroJsonPath) - return new NitroConfig(config) + return new NitroConfig(config, filePath) } } } diff --git a/packages/react-native-nitro-modules/Package.swift b/packages/react-native-nitro-modules/Package.swift new file mode 100644 index 0000000000..0cf330b9ba --- /dev/null +++ b/packages/react-native-nitro-modules/Package.swift @@ -0,0 +1,105 @@ +// swift-tools-version: 6.0 +// NitroModules — SwiftPM manifest (React Native SwiftPM Preview support) +// +// Consumed by React Native's SwiftPM autolinking as a "self-managed" +// package: the autolinker references this directory through a symlink at +// /ios/build/generated/autolinking/libs/NitroModules/, and SwiftPM +// resolves the relative `.package(path:)` references below against that +// symlink location — NOT against this file's real directory. That makes +// the paths app-layout-independent: +// ../../../../xcframeworks -> /ios/build/xcframeworks (ReactNative) +// ../../../ios -> /ios/build/generated/ios (React-GeneratedCode) +// +// SwiftPM cannot compile Swift and C++ in a single target (CocoaPods can), +// so the pod's single mixed target is split: +// - NitroModulesCore (C++/ObjC++): cpp/** + ios C-family sources. +// Public headers live in include/NitroModules/. +// - NitroModules (Swift): ios/**/*.swift with C++ interop enabled. +// Re-exports NitroModulesCore (ios/utils/SwiftPMExports.swift) so +// `import NitroModules` behaves like the mixed CocoaPods module. + +import PackageDescription + +let reactCxxDefines: [CXXSetting] = [ + // Match the prebuilt React.framework's config-gated C++ ABI: NDEBUG in + // Release strips DebugStringConvertible's vtable / shifts ShadowNode's + // layout, so mismatching defines fail at link time. + .define("DEBUG", .when(configuration: .debug)), + .define("NDEBUG", .when(configuration: .release)), + // React Native's prebuilt SwiftPM distribution is New Architecture only. + .define("RCT_NEW_ARCH_ENABLED", to: "1"), +] + + +let package = Package( + name: "NitroModules", + platforms: [.iOS(.v15), .visionOS(.v1)], + products: [ + // Only the Swift half is vended: NitroModulesCore comes along as its + // target dependency, and SwiftPM propagates a transitive clang target's + // public headers, modulemap and objects to whoever depends on the + // product. So dependents still get `import NitroModules` AND + // `#include ` without naming Core anywhere. + .library(name: "NitroModules", targets: ["NitroModules"]), + ], + dependencies: [ + .package(name: "ReactNative", path: "../../../../xcframeworks"), + .package(name: "React-GeneratedCode", path: "../../../ios"), + ], + targets: [ + .target( + name: "NitroModulesCore", + dependencies: [ + .product(name: "ReactHeaders", package: "ReactNative"), + .product(name: "ReactNativeHeaders", package: "ReactNative"), + .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), + // The app's codegen output — serves . + .product(name: "ReactAppHeaders", package: "React-GeneratedCode"), + ], + path: ".", + sources: [ + "cpp", + "ios/platform", + "ios/threading", + "ios/turbomodule", + ], + publicHeadersPath: "include", + cxxSettings: [ + // The headers live in their normal homes (cpp/**, ios/**) and + // include each other by bare name (CocoaPods resolves that via + // headermaps). include/NitroModules/ is the flat symlink tree + // staged at `react-native spm add` time by the autolinking + // plugin — one search path resolves every bare-name include. + .headerSearchPath("include/NitroModules") + ] + reactCxxDefines, + linkerSettings: [ + .linkedFramework("Foundation"), + .linkedFramework("UIKit", .when(platforms: [.iOS])), + ] + ), + .target( + name: "NitroModules", + dependencies: ["NitroModulesCore"], + path: "ios", + sources: [ + "core/AnyMap.swift", + "core/ArrayBuffer.swift", + "core/HybridObject.swift", + "core/Null.swift", + "core/Promise.swift", + "core/RuntimeError.swift", + "utils/Date+fromChrono.swift", + "utils/MemoryHelper.swift", + "utils/SwiftClosure.swift", + "utils/SwiftPMExports.swift", + "views/HybridView.swift", + "views/RecyclableView.swift", + ], + swiftSettings: [ + .interoperabilityMode(.Cxx), + .swiftLanguageMode(.v5), + ] + ), + ], + cxxLanguageStandard: .cxx20 +) diff --git a/packages/react-native-nitro-modules/cpp/utils/AssertPromiseState.hpp b/packages/react-native-nitro-modules/cpp/utils/AssertPromiseState.hpp index c89cc029aa..45ec79018f 100644 --- a/packages/react-native-nitro-modules/cpp/utils/AssertPromiseState.hpp +++ b/packages/react-native-nitro-modules/cpp/utils/AssertPromiseState.hpp @@ -10,6 +10,8 @@ namespace margelo::nitro { template class Promise; + +enum PromiseTask { WANTS_TO_RESOLVE, WANTS_TO_REJECT }; } // namespace margelo::nitro #include "NitroTypeInfo.hpp" @@ -19,8 +21,6 @@ class Promise; namespace margelo::nitro { -enum PromiseTask { WANTS_TO_RESOLVE, WANTS_TO_REJECT }; - template void assertPromiseState(Promise& promise, PromiseTask task) { if (!promise.isPending()) [[unlikely]] { diff --git a/packages/react-native-nitro-modules/ios/utils/SwiftPMExports.swift b/packages/react-native-nitro-modules/ios/utils/SwiftPMExports.swift new file mode 100644 index 0000000000..f14afe19ea --- /dev/null +++ b/packages/react-native-nitro-modules/ios/utils/SwiftPMExports.swift @@ -0,0 +1,14 @@ +// +// SwiftPMExports.swift +// NitroModules +// +// SwiftPM-only: re-export the C++ core (the NitroModulesCore clang target) +// so that `import NitroModules` exposes Nitro's C++ types +// (margelo.nitro.ArrayBufferHolder, margelo.nitro.TestPromiseHolder, ...) +// exactly like the single mixed-language CocoaPods module does. +// Under CocoaPods this file compiles to nothing (no such module). +// + +#if canImport(NitroModulesCore) + @_exported import NitroModulesCore +#endif diff --git a/packages/react-native-nitro-modules/package.json b/packages/react-native-nitro-modules/package.json index fc3f32a99d..a75e034aed 100644 --- a/packages/react-native-nitro-modules/package.json +++ b/packages/react-native-nitro-modules/package.json @@ -22,7 +22,9 @@ "app.plugin.js", "*.podspec", "nitro_pod_utils.rb", - "README.md" + "README.md", + "Package.swift", + "scripts/rn-spm-autolinking-plugin.cjs" ], "keywords": [ "react-native", @@ -73,9 +75,9 @@ "@types/jest": "*", "@types/react": "^19.2.15", "jest": "*", - "@react-native/jest-preset": "0.85.3", + "@react-native/jest-preset": "0.87.0", "react": "19.2.3", - "react-native": "0.85.3", + "react-native": "0.87.0", "react-native-builder-bob": "^0.41.0", "react-native-worklets": "^0.9.1" }, diff --git a/packages/react-native-nitro-modules/react-native.config.js b/packages/react-native-nitro-modules/react-native.config.js index 3fdf8eaadc..c34a349da6 100644 --- a/packages/react-native-nitro-modules/react-native.config.js +++ b/packages/react-native-nitro-modules/react-native.config.js @@ -1,6 +1,13 @@ // https://github.com/react-native-community/cli/blob/main/docs/dependencies.md module.exports = { + spm: { + name: 'NitroModules', + // Vends Xcode's generated Swift C++-interop headers (-Swift.h) + // to all self-managed packages via a stable `.spm-derived-headers` + // symlink — see the plugin for details. + autolinkingPlugin: './scripts/rn-spm-autolinking-plugin.cjs', + }, dependency: { platforms: { /** diff --git a/packages/react-native-nitro-modules/scripts/rn-spm-autolinking-plugin.cjs b/packages/react-native-nitro-modules/scripts/rn-spm-autolinking-plugin.cjs new file mode 100644 index 0000000000..044beec26b --- /dev/null +++ b/packages/react-native-nitro-modules/scripts/rn-spm-autolinking-plugin.cjs @@ -0,0 +1,550 @@ +/** + * React Native SwiftPM (Preview) autolinking plugin for Nitro Modules. + * + * Declared in react-native.config.js (`spm.autolinkingPlugin`) — React + * Native's SwiftPM autolinking discovers it transitively and invokes it on + * every regeneration: the terminal `spm add` / `update`, and the in-build + * syncs (scheme pre-action + app-target build phase). + * + * Do NOT rely on the in-build syncs for anything a SwiftPM target needs at + * compile time: `xcodebuild` does not execute scheme pre-actions, and the + * build phase belongs to the APP target, which builds AFTER the package + * targets. Only the terminal run is guaranteed to precede a compile. + * + * It does four things: + * + * 1. CONTRIBUTES NitroModules ITSELF. React Native skips normal autolinking + * for a dependency that hosts a plugin (it assumes an Expo-style pure-JS + * host), so the plugin re-adds this package the same way the self-managed + * path would have: a `libs/NitroModules` symlink (stable SwiftPM identity, + * matching the `.package(path: "../NitroModules")` references in every + * nitrogen-generated module manifest) plus package/product contributions + * for the app's aggregator. + * + * 2. WARNS ABOUT NITRO MODULES THAT HAVE NOT OPTED INTO SwiftPM + * (no `spm.name` in their react-native.config.js). Note this only reaches + * the user for single-language (C++-only) modules — see + * findNitroModulesWithoutSwiftPM for why. + * + * 3. STAGES EVERY NITRO PACKAGE'S PUBLIC HEADERS (see stagePublicHeaders) — + * the flat symlink tree behind each manifest's `publicHeadersPath`, and + * the (empty) `include-bridge/` dir for the SwiftBridge targets. The + * SwiftPM analogue of CocoaPods staging `Pods/Headers/Public//` at + * `pod install` time: the source tree ships no placeholder or alias files + * at all. + * + * 4. VENDS XCODE'S GENERATED INTEROP HEADERS. Xcode stages every SwiftPM + * Swift target's generated C++ interop header (`-Swift.h`) under + * `$OBJROOT/GeneratedModuleMaps-/` in DerivedData, and only + * vends it to dependent targets as a clang module — which a C++-interop + * header cannot be consumed as (its C++ declarations must be in scope + * BEFORE the header is parsed). Nitro's Swift-Cxx umbrella includes it + * TEXTUALLY instead. Since a static Package.swift cannot know the + * DerivedData path, the plugin symlinks it to a stable location inside + * every self-managed package: + * + * /.spm-derived-headers -> $OBJROOT + * + * The nitrogen-generated manifests add + * `.headerSearchPath(".spm-derived-headers")`, and the umbrella picks the + * platform subdirectory (`GeneratedModuleMaps-iphonesimulator/…`) via + * TargetConditionals — the link is platform-independent and survives + * simulator/device switches without a re-sync. + * + * Because the link must exist BEFORE any SwiftPM target compiles, and the + * in-build syncs cannot guarantee that (see above), OBJROOT is resolved + * from the environment when Xcode provides it and otherwise queried via + * `xcodebuild -showBuildSettings` during the terminal run. + * + * Under plain `swift build` (no Xcode) the header is vended natively by + * SwiftPM and nothing else is needed. + * + * The plugin writes only into Nitro's own package directories and the + * autolinker's `libs/` alias dir — never React Native's generated manifests. + * + * RN treats a throwing plugin as fatal, so this one throws for exactly one + * class of problem: a misconfiguration the developer must fix (see + * NitroConfigurationError). Every other failure degrades to a warning — a + * plugin hiccup must not break an otherwise working build. + */ +const fs = require('fs') +const path = require('path') +const { execFileSync } = require('child_process') + +const NITRO_PACKAGE_ROOT = path.resolve(__dirname, '..') +const SWIFT_NAME = 'NitroModules' + +/** + * A misconfiguration the developer must fix (as opposed to an environment + * failure the plugin can degrade around). Thrown out of the plugin so React + * Native stops with the message intact. + */ +class NitroConfigurationError extends Error {} + +/** + * Create/refresh `linkPath -> target`. Returns null on success, or a message + * describing the failure — callers surface it. A silent failure here shows up + * thousands of lines later as an unintelligible C++ error about incomplete + * types, so a read-only store (pnpm) must degrade LOUDLY, not invisibly. + */ +function ensureSymlink(linkPath, target) { + try { + // lstat, NOT existsSync: existsSync follows the link and reports `false` + // for a DANGLING one (a stale DerivedData that has since been cleaned), + // which would leave the stale link in place. + let current = null + try { + if (fs.lstatSync(linkPath).isSymbolicLink()) { + current = fs.readlinkSync(linkPath) + } + } catch { + // Nothing there yet. + } + if (current === target) return null + fs.rmSync(linkPath, { recursive: false, force: true }) + fs.symlinkSync(target, linkPath) + return null + } catch (e) { + return `${linkPath}: ${e.code ?? String(e)}` + } +} + +/** + * Where Xcode stages each Swift target's generated C++ interop header + * (`-Swift.h`), i.e. `$OBJROOT/GeneratedModuleMaps-/`. + * + * OBJROOT is only exported in some of the contexts this plugin runs in — the + * scheme pre-action and the terminal `spm add`/`update` may not have it — so + * fall back to deriving it from the other build settings Xcode does export. + * Returns null when there is no Xcode environment at all (plain `swift build`, + * where SwiftPM vends the header natively and no link is needed). + */ +function resolveObjRoot() { + const direct = process.env.OBJROOT + if (direct != null && direct.length > 0) return direct + // OBJROOT is conventionally a sibling of SYMROOT inside DerivedData/Build: + //
/Build/Products (SYMROOT) ->
/Build/Intermediates.noindex + const symRoot = process.env.SYMROOT ?? process.env.BUILD_DIR + if (symRoot != null && symRoot.length > 0) { + const buildDir = path.dirname(symRoot.replace(/\/+$/, '')) + const derived = path.join(buildDir, 'Intermediates.noindex') + if (fs.existsSync(derived)) return derived + } + // PROJECT_TEMP_DIR = $OBJROOT/.build — walk up one level. + const projectTemp = process.env.PROJECT_TEMP_DIR + if (projectTemp != null && projectTemp.length > 0) { + const up = path.dirname(projectTemp.replace(/\/+$/, '')) + if (fs.existsSync(up)) return up + } + return null +} + +/** + * Ask Xcode where it stages intermediates for this project. + * + * Needed because the link must exist BEFORE any SwiftPM target compiles, and + * none of the in-build hooks can guarantee that: RN's sync runs as a scheme + * pre-action (which `xcodebuild` does not execute) and as a build phase of the + * APP target — which builds *after* the package targets that consume the + * header. So the link has to be created from the terminal, during + * `react-native spm add` / `update`, where no build settings are exported. + * + * OBJROOT is stable per project (it does not vary with configuration or + * destination), so one query is enough. Best-effort and slow (~seconds), hence + * only used as the last resort, and only when a project can be found. + */ +function queryObjRootFromXcode(context) { + const appRoot = context?.appRoot ?? context?.projectRoot + if (typeof appRoot !== 'string' || appRoot.length === 0) return null + let projectDir = appRoot + let project = findXcodeproj(projectDir) + if (project == null) { + projectDir = path.join(appRoot, 'ios') + project = findXcodeproj(projectDir) + } + if (project == null) return null + const projectPath = path.join(projectDir, project) + const run = (args) => + execFileSync('xcodebuild', args, { + encoding: 'utf8', + timeout: 180000, + stdio: ['ignore', 'pipe', 'ignore'], + }) + try { + // A SCHEME is required: without one, xcodebuild reports the legacy + // project-local `/build` for OBJROOT instead of the DerivedData + // path an actual `-scheme` build uses — linking to it would silently point + // the umbrella at a directory Xcode never writes the interop header into. + const listed = JSON.parse(run(['-project', projectPath, '-list', '-json'])) + const schemes = listed?.project?.schemes + if (!Array.isArray(schemes) || schemes.length === 0) return null + // Prefer the APP scheme (named after the .xcodeproj). The list also + // contains one scheme per SwiftPM package (`Autolinked`, `NitroTest`, …) + // and those sort first alphabetically — querying one of those would report + // a different OBJROOT than the app build actually uses. + const appScheme = path.basename(project, '.xcodeproj') + const scheme = schemes.includes(appScheme) ? appScheme : schemes[0] + if (typeof scheme !== 'string' || scheme.length === 0) return null + const out = run([ + '-project', + projectPath, + '-scheme', + scheme, + '-showBuildSettings', + ]) + const match = out.match(/^\s*OBJROOT = (.+)$/m) + const value = match?.[1]?.trim() + return value != null && value.length > 0 ? value : null + } catch { + // No Xcode, unresolvable project/scheme, or the query timed out — the + // caller reports the resulting failure. + return null + } +} + +function findXcodeproj(dir) { + try { + return ( + fs.readdirSync(dir).find((entry) => entry.endsWith('.xcodeproj')) ?? null + ) + } catch { + return null + } +} + +/** + * Real directories of every package that may contain a nitrogen-generated + * manifest: Nitro core itself plus every autolinked dependency. Resolved to + * real paths so the link lands next to the package's own `Package.swift` + * regardless of how many symlinks (workspace links, `libs/` aliases) point at + * it — and so two aliases for one package can't create two competing links. + * + * Linking a non-Nitro dependency is harmless: `.spm-derived-headers` is only + * ever read by a manifest that declares it as a header search path. + */ +function collectPackageRoots(context) { + const roots = new Set() + const add = (dir) => { + if (typeof dir !== 'string' || dir.length === 0) return + try { + roots.add(fs.realpathSync(dir)) + } catch { + // Dependency root does not exist — nothing to link. + } + } + add(NITRO_PACKAGE_ROOT) + const deps = context?.autolinking?.dependencies + if (deps != null && typeof deps === 'object') { + for (const dep of Object.values(deps)) { + add(dep?.root) + } + } + return roots +} + +/** + * Stages a Nitro package's public headers into its SwiftPM + * `publicHeadersPath` directory as a FLAT tree of symlinks: + * + * core: /include/NitroModules/
.hpp -> ../../cpp/… + * modules: /nitrogen/generated/include//
.hpp -> … + * + * This is the SwiftPM analogue of what CocoaPods does at `pod install` time + * (staging `Pods/Headers/Public//` + headermaps): it is what makes the + * public `#include ` spelling — and the bare-name includes + * between Nitro headers — resolve for consumers, because SwiftPM propagates + * ONLY the one `publicHeadersPath` directory and has no staging step of its + * own. The headers themselves stay exactly where they live in the source + * tree; only symlinks are created, at `spm add`/`update` time, so nothing is + * committed (beyond a `.gitkeep`) and nothing ships in the npm tarball — + * npm drops symlinks when packing, which is why the tree cannot simply be + * checked in. + * + * Returns a list of failure messages (empty on success). + */ +function stagePublicHeaders(root) { + // Every return below MUST have this shape — the caller spreads both fields. + const nothing = { warnings: [], conflicts: [] } + const isCore = root === fs.realpathSync(NITRO_PACKAGE_ROOT) + // Module packages: only stage packages that carry a nitrogen-generated + // SwiftPM manifest — other autolinked deps (e.g. react-native-screens) have + // no Nitro header tree to stage. + if (!isCore && !fs.existsSync(path.join(root, 'nitrogen', 'generated'))) { + return nothing + } + const manifest = path.join(root, 'Package.swift') + if (!fs.existsSync(manifest)) return nothing + + // The staging dir name must equal the SWIFT MODULE name consumers write in + // `#include ` (nitro.json's `ios.iosModuleName`) — NOT the + // SwiftPM package name in Package.swift, which React Native derives from the + // npm package name and is usually different (e.g. package + // `ReactNativeNitroTest` contains module `NitroTest`). + let moduleName = SWIFT_NAME + if (!isCore) { + try { + const nitroJson = JSON.parse( + fs.readFileSync(path.join(root, 'nitro.json'), 'utf8') + ) + const name = nitroJson?.ios?.iosModuleName + if (typeof name !== 'string' || name.length === 0) return nothing + moduleName = name + } catch { + // Not a Nitro module (or unreadable config) — nothing to stage. + return nothing + } + } + + // Collect the package's public headers (they stay in place). + const headers = [] + const collect = (rel, exts = ['.hpp', '.h']) => { + const abs = path.join(root, rel) + if (!fs.existsSync(abs)) return + for (const entry of fs.readdirSync(abs, { withFileTypes: true })) { + const relPath = path.join(rel, entry.name) + if (entry.isDirectory()) collect(relPath, exts) + else if (exts.some((e) => entry.name.endsWith(e))) headers.push(relPath) + } + } + if (isCore) { + // .hpp only: the C++ module's public surface. ObjC headers (e.g. the + // TurboModule's NativeNitroModules.h) are not part of it. + collect('cpp', ['.hpp']) + collect('ios', ['.hpp']) + } else { + collect('cpp') + collect(path.join('nitrogen', 'generated', 'shared', 'c++')) + const bridge = path.join( + 'nitrogen', + 'generated', + 'ios', + `${moduleName}-Swift-Cxx-Bridge.hpp` + ) + if (fs.existsSync(path.join(root, bridge))) headers.push(bridge) + } + + const stagingDir = isCore + ? path.join(root, 'include', SWIFT_NAME) + : path.join(root, 'nitrogen', 'generated', 'include', moduleName) + + const warnings = [] + const conflicts = [] + try { + fs.mkdirSync(stagingDir, { recursive: true }) + // The SwiftBridge target's publicHeadersPath — it exposes no public + // headers, but SwiftPM requires the directory to exist at build time. + // Created empty here for the same reason the staging dir is: placeholders + // don't belong in the source tree or the npm tarball. Only modules with a + // Swift part HAVE a SwiftBridge target — a C++-only module is a single + // target and must not get a stray unused directory. + if (!isCore && hasSwiftPart(root)) { + fs.mkdirSync(path.join(root, 'nitrogen', 'generated', 'include-bridge'), { + recursive: true, + }) + } + } catch (e) { + return { warnings: [`${stagingDir}: ${e.code ?? String(e)}`], conflicts } + } + + const wanted = new Map() + for (const header of headers) { + const name = path.basename(header) + const previous = wanted.get(name) + if (previous != null) { + // A CONFIGURATION error, not an environment hiccup: the two headers can + // never both be reachable as . Reported as fatal so it stops + // here, instead of surfacing later as a bewildering "file not found" on + // whichever header lost the race. + conflicts.push( + ` ${moduleName}: duplicate header name "${name}"\n` + + ` ${previous}\n ${header}` + ) + continue + } + wanted.set(name, header) + const failure = ensureSymlink( + path.join(stagingDir, name), + path.relative(stagingDir, path.join(root, header)) + ) + if (failure != null) warnings.push(failure) + } + + // Drop stale aliases for headers that no longer exist (keep `.gitkeep`). + try { + for (const entry of fs.readdirSync(stagingDir)) { + if (entry === '.gitkeep' || wanted.has(entry)) continue + fs.rmSync(path.join(stagingDir, entry), { force: true }) + } + } catch { + // Purely cosmetic cleanup — never fail staging over it. + } + return { warnings, conflicts } +} + +/** + * Whether this module has Swift HybridObjects — i.e. nitrogen emitted Swift + * sources for it, which is exactly when its manifest declares the Swift and + * `SwiftBridge` targets. A C++-only module is a single target. + */ +function hasSwiftPart(root) { + return fs.existsSync(path.join(root, 'nitrogen', 'generated', 'ios', 'swift')) +} + +/** + * Nitro modules that have no `Package.swift`, i.e. that have not opted into + * SwiftPM support, i.e. that declare no `spm.name` in react-native.config.js. + * + * Without a manifest React Native falls back to auto-scaffolding one from the + * podspec, which says nothing about Nitro or about the one-line fix. + * + * IMPORTANT — this only reaches the user for modules RN can still scaffold, + * i.e. SINGLE-LANGUAGE ones (a C++-only Nitro module). React Native generates + * autolinked targets long BEFORE it invokes plugins, so for a Swift-backed + * module it has already failed with + * + * error: "" has mixed Swift + Objective-C/C++ sources, which Swift + * Package Manager cannot compile in a single target … + * + * by the time this runs. That error is the one most users will actually see; + * making it mention Nitro requires a change in React Native, not here. + */ +function findNitroModulesWithoutSwiftPM(context) { + const found = [] + const deps = context?.autolinking?.dependencies + if (deps == null || typeof deps !== 'object') return found + let corePath = null + try { + corePath = fs.realpathSync(NITRO_PACKAGE_ROOT) + } catch { + // Ignore — only used to skip Nitro core itself. + } + for (const [name, dep] of Object.entries(deps)) { + const root = dep?.root + if (typeof root !== 'string' || root.length === 0) continue + try { + const real = fs.realpathSync(root) + if (real === corePath) continue + const isNitroModule = + fs.existsSync(path.join(real, 'nitro.json')) || + fs.existsSync(path.join(real, 'nitrogen', 'generated')) + if (!isNitroModule) continue + if (fs.existsSync(path.join(real, 'Package.swift'))) continue + found.push(name) + } catch { + // Unreadable dependency root — nothing to report. + } + } + return found +} + +module.exports = function nitroSpmAutolinkingPlugin(context) { + const contribution = { + packageDependencies: [], + productDependencies: [], + watchPaths: [path.join(NITRO_PACKAGE_ROOT, 'Package.swift')], + } + try { + const libsDir = path.join(context.outputDir, 'libs') + fs.mkdirSync(libsDir, { recursive: true }) + + // 1. Re-add NitroModules itself (RN skipped it as the plugin host). + // Routing through libs/NitroModules keeps ONE SwiftPM identity with the + // `.package(name: "NitroModules", path: "../NitroModules")` references + // in every nitro module's generated Package.swift. + ensureSymlink(path.join(libsDir, SWIFT_NAME), NITRO_PACKAGE_ROOT) + contribution.packageDependencies.push({ + name: SWIFT_NAME, + path: `libs/${SWIFT_NAME}`, + }) + contribution.productDependencies.push({ + name: SWIFT_NAME, + package: SWIFT_NAME, + }) + + // 2. Point at the real cause. Only reachable for single-language + // modules — RN aborts on mixed-language ones before invoking plugins. + const notOptedIn = findNitroModulesWithoutSwiftPM(context) + if (notOptedIn.length > 0) { + const list = notOptedIn.map((n) => ` - ${n}`).join('\n') + console.warn( + `[NitroModules] These Nitro modules have no Package.swift, so React ` + + `Native will try to scaffold one and fail with "has mixed Swift + ` + + `Objective-C/C++ sources":\n${list}\n` + + ` To fix, in each module's react-native.config.js set:\n` + + ` spm: { name: '' }\n` + + ` then re-run nitrogen in that module (the config is a codegen ` + + `input — \`react-native spm\` does not re-run it).` + ) + } + + // 3. Stage every Nitro package's public headers into its SwiftPM + // `publicHeadersPath` dir (see stagePublicHeaders). Two failure + // classes: a CONFIGURATION conflict is fatal here (it can only ever + // produce a bewildering "file not found" later), while an environment + // hiccup (read-only store) is loud but non-fatal. + { + const warnings = [] + const conflicts = [] + for (const root of collectPackageRoots(context)) { + const result = stagePublicHeaders(root) + warnings.push(...result.warnings) + conflicts.push(...result.conflicts) + } + if (conflicts.length > 0) { + throw new NitroConfigurationError( + `Nitro modules expose all of their public headers from a single ` + + `include directory, so header file names must be unique within a ` + + `module. Rename one of each pair below and re-run nitrogen:\n` + + conflicts.join('\n') + ) + } + if (warnings.length > 0) { + console.warn( + `[NitroModules] Could not stage public headers for SwiftPM — ` + + `dependent modules will fail to compile with "/….hpp ` + + `file not found". Failures:\n ` + + warnings.join('\n ') + ) + } + } + + // 4. Vend Xcode's generated interop headers. + // + // The link must exist BEFORE any SwiftPM target compiles. Package roots + // are derived from the autolinking dep list rather than by listing + // `libs/`, because RN populates `libs/` for self-managed packages AFTER + // invoking plugins — listing it here sees only the previous run's + // entries, so a clean build would link nothing and the SwiftBridge targets + // would fail on a missing `-Swift.h`. + const objRoot = resolveObjRoot() ?? queryObjRootFromXcode(context) + if (objRoot != null) { + const failures = [] + for (const root of collectPackageRoots(context)) { + const failure = ensureSymlink( + path.join(root, '.spm-derived-headers'), + objRoot + ) + if (failure != null) failures.push(failure) + } + if (failures.length > 0) { + console.warn( + `[NitroModules] Could not stage Xcode's generated Swift interop ` + + `headers — Swift-backed HybridObjects will fail to compile with ` + + `"autogenerated Swift header cannot be found". Failures:\n ` + + failures.join('\n ') + ) + } + } + } catch (e) { + // A misconfiguration the developer must fix propagates: React Native + // wraps it ("the autolinking plugin for 'X' threw: …") and stops, which + // is far better than letting the build fail later for an unrelated-looking + // reason. Everything else stays non-fatal — a plugin hiccup must not be + // the thing that breaks an otherwise working build. + if (e instanceof NitroConfigurationError) throw e + console.warn( + `[NitroModules] SwiftPM autolinking plugin failed (build may miss Nitro): ${String(e)}` + ) + } + return contribution +} diff --git a/packages/react-native-nitro-modules/src/views/getHostComponent.ts b/packages/react-native-nitro-modules/src/views/getHostComponent.ts index b01cc4d585..cbb87bdec8 100644 --- a/packages/react-native-nitro-modules/src/views/getHostComponent.ts +++ b/packages/react-native-nitro-modules/src/views/getHostComponent.ts @@ -1,7 +1,9 @@ -import { Platform, type HostComponent, type ViewProps } from 'react-native' -// TODO: Migrate to the official export of `NativeComponentRegistry` from `react-native` once react-native 0.83.0 becomes more established as this is deprecated -// eslint-disable-next-line @react-native/no-deep-imports -import * as NativeComponentRegistry from 'react-native/Libraries/NativeComponent/NativeComponentRegistry' +import { + NativeComponentRegistry, + Platform, + type HostComponent, + type ViewProps, +} from 'react-native' import type { HybridView, HybridViewMethods, diff --git a/packages/react-native-nitro-test-external/Package.swift b/packages/react-native-nitro-test-external/Package.swift new file mode 100644 index 0000000000..b113ecbcba --- /dev/null +++ b/packages/react-native-nitro-test-external/Package.swift @@ -0,0 +1,132 @@ +// swift-tools-version: 6.0 +/// +/// Package.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// +// +// React Native SwiftPM manifest for NitroTestExternal. Consumed by RN's +// SwiftPM autolinking as a "self-managed" package through the +// /ios/build/generated/autolinking/libs/NitroTestExternal/ symlink — the +// relative package paths below resolve against that symlink location: +// ../../../../xcframeworks -> /ios/build/xcframeworks (ReactNative) +// ../NitroModules -> libs/NitroModules (nitro core) +// +// NOTE: these path references assume React Native SwiftPM's default +// local-artifacts mode (xcframeworks downloaded into /ios/build/). +// RN's experimental remote-package mode (RN_SPM_REMOTE_URL) is not yet +// supported by generated Nitro manifests. +// + +import PackageDescription + +let reactCxxDefines: [CXXSetting] = [ + // Match the prebuilt React.framework's config-gated C++ ABI. + .define("DEBUG", .when(configuration: .debug)), + .define("NDEBUG", .when(configuration: .release)), + // React Native's prebuilt SwiftPM distribution is New Architecture only. + .define("RCT_NEW_ARCH_ENABLED", to: "1"), +] + +let headerDirs: [CXXSetting] = [ + .headerSearchPath("nitrogen/generated/shared/c++"), + .headerSearchPath("nitrogen/generated/shared/c++/views"), + .headerSearchPath("nitrogen/generated/ios"), + .headerSearchPath("nitrogen/generated/ios/c++"), + .headerSearchPath("nitrogen/generated/ios/c++/views"), +] + +let package = Package( + name: "ReactNativeNitroTestExternal", + platforms: [.iOS(.v15), .visionOS(.v1)], + products: [ + // Only the root of the target chain is vended - SwiftPM pulls + // NitroTestExternal (Swift) and NitroTestExternalCore (C++) in as its target + // dependencies, propagating their module, public headers and objects + // to dependents. Naming them here would add nothing. + .library(name: "ReactNativeNitroTestExternal", targets: ["NitroTestExternalSwiftBridge"]), + ], + dependencies: [ + .package(name: "ReactNative", path: "../../../../xcframeworks"), + .package(name: "NitroModules", path: "../NitroModules"), + + ], + targets: [ + // C++ layer: nitrogen's cross-platform specs + the user's C++ hybrids. + // No Swift dependency — this is what the Swift target imports. + .target( + name: "NitroTestExternalCore", + dependencies: [ + .product(name: "NitroModules", package: "NitroModules"), + + .product(name: "ReactHeaders", package: "ReactNative"), + .product(name: "ReactNativeHeaders", package: "ReactNative"), + .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), + ], + path: ".", + sources: [ + "nitrogen/generated/shared/c++", + ], + publicHeadersPath: "nitrogen/generated/include", + cxxSettings: headerDirs + reactCxxDefines + ), + // Swift layer: nitrogen's Swift specs/_cxx bridges + user Swift hybrids. + .target( + name: "NitroTestExternal", + dependencies: [ + "NitroTestExternalCore", + .product(name: "NitroModules", package: "NitroModules"), + + ], + path: ".", + sources: [ + "ios/HybridSomeExternalObject.swift", + "nitrogen/generated/ios/swift", + "nitrogen/generated/ios/NitroTestExternalAutolinking.swift", + ], + swiftSettings: [ + .interoperabilityMode(.Cxx), + .swiftLanguageMode(.v5), + ] + ), + // SwiftBridge layer: every C++/ObjC++ TU that consumes the Swift-generated + // interface header (NitroTestExternal-Swift.h). Structurally required: + // these TUs cannot live in NitroTestExternalCore (Core -> Swift -> Core + // cycle) nor in the Swift target (SwiftPM forbids mixed languages). + .target( + name: "NitroTestExternalSwiftBridge", + dependencies: [ + "NitroTestExternal", + "NitroTestExternalCore", + .product(name: "NitroModules", package: "NitroModules"), + + .product(name: "ReactHeaders", package: "ReactNative"), + .product(name: "ReactNativeHeaders", package: "ReactNative"), + .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), + ], + path: ".", + sources: [ + + "nitrogen/generated/ios/c++", + "nitrogen/generated/ios/NitroTestExternal-Swift-Cxx-Bridge.cpp", + "nitrogen/generated/ios/NitroTestExternalAutolinking.mm", + ], + publicHeadersPath: "nitrogen/generated/include-bridge", + cxxSettings: headerDirs + reactCxxDefines + [ + // Under Xcode, React Native's SPM sync symlinks + // $OBJROOT/GeneratedModuleMaps- (where Xcode stages + // the Swift-generated NitroTestExternal-Swift.h) to this stable + // path, so the Swift-Cxx umbrella can include it TEXTUALLY. + // Under `swift build` SwiftPM vends the header natively and + // this path simply doesn't exist. + .headerSearchPath(".spm-derived-headers"), + ], + linkerSettings: [ + .linkedFramework("Foundation"), + .linkedFramework("UIKit", .when(platforms: [.iOS])), + ] + ), + ], + cxxLanguageStandard: .cxx20 +) diff --git a/packages/react-native-nitro-test-external/nitrogen/generated/android/NitroTestExternal+autolinking.cmake b/packages/react-native-nitro-test-external/nitrogen/generated/android/NitroTestExternal+autolinking.cmake index 79c9340a28..5b1dff6bda 100644 --- a/packages/react-native-nitro-test-external/nitrogen/generated/android/NitroTestExternal+autolinking.cmake +++ b/packages/react-native-nitro-test-external/nitrogen/generated/android/NitroTestExternal+autolinking.cmake @@ -34,6 +34,7 @@ target_sources( ../nitrogen/generated/android/NitroTestExternalOnLoad.cpp # Shared Nitrogen C++ sources ../nitrogen/generated/shared/c++/HybridSomeExternalObjectSpec.cpp + ../nitrogen/generated/shared/c++/OptionalPrimitivesHolder.cpp # Android-specific Nitrogen C++ sources ../nitrogen/generated/android/c++/JHybridSomeExternalObjectSpec.cpp ) diff --git a/packages/react-native-nitro-test-external/nitrogen/generated/ios/NitroTestExternal-Swift-Cxx-Umbrella.hpp b/packages/react-native-nitro-test-external/nitrogen/generated/ios/NitroTestExternal-Swift-Cxx-Umbrella.hpp index a08536f9e0..165606f58a 100644 --- a/packages/react-native-nitro-test-external/nitrogen/generated/ios/NitroTestExternal-Swift-Cxx-Umbrella.hpp +++ b/packages/react-native-nitro-test-external/nitrogen/generated/ios/NitroTestExternal-Swift-Cxx-Umbrella.hpp @@ -44,5 +44,21 @@ namespace NitroTestExternal { class HybridSomeExternalObjectSpec_cxx; } #elif __has_include() #include #else +// React Native SwiftPM (Preview): Xcode stages the generated header in +// DerivedData ($OBJROOT/GeneratedModuleMaps-/). Nitro's RN SwiftPM +// autolinking plugin symlinks $OBJROOT to `.spm-derived-headers` inside this +// package (a headerSearchPath of the glue target), and TargetConditionals +// picks the platform subdirectory. +#include +#if TARGET_OS_VISION && TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-xrsimulator/NitroTestExternal-Swift.h") +#include "GeneratedModuleMaps-xrsimulator/NitroTestExternal-Swift.h" +#elif TARGET_OS_VISION && __has_include("GeneratedModuleMaps-xros/NitroTestExternal-Swift.h") +#include "GeneratedModuleMaps-xros/NitroTestExternal-Swift.h" +#elif TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-iphonesimulator/NitroTestExternal-Swift.h") +#include "GeneratedModuleMaps-iphonesimulator/NitroTestExternal-Swift.h" +#elif !TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-iphoneos/NitroTestExternal-Swift.h") +#include "GeneratedModuleMaps-iphoneos/NitroTestExternal-Swift.h" +#else #error NitroTestExternal's autogenerated Swift header cannot be found! Make sure the Swift module's name (= podspec name) is actually "NitroTestExternal", and try building the app first. #endif +#endif diff --git a/packages/react-native-nitro-test-external/nitrogen/generated/ios/swift/NitroTestExternalExports.swift b/packages/react-native-nitro-test-external/nitrogen/generated/ios/swift/NitroTestExternalExports.swift new file mode 100644 index 0000000000..da8c338e6b --- /dev/null +++ b/packages/react-native-nitro-test-external/nitrogen/generated/ios/swift/NitroTestExternalExports.swift @@ -0,0 +1,17 @@ +/// +/// NitroTestExternalExports.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +// Re-exports this module's C++ half (the NitroTestExternalCore clang target) so +// that every OTHER Swift file in this module sees nitrogen's generated C++ +// specs WITHOUT importing it itself — `@_exported import` is visible module- +// wide, not just in this file. +// +// Under CocoaPods the module is a single mixed target, so no NitroTestExternalCore +// module exists and this file compiles to nothing. +#if canImport(NitroTestExternalCore) +@_exported import NitroTestExternalCore +#endif diff --git a/packages/react-native-nitro-test-external/nitrogen/generated/shared/c++/OptionalPrimitivesHolder.cpp b/packages/react-native-nitro-test-external/nitrogen/generated/shared/c++/OptionalPrimitivesHolder.cpp new file mode 100644 index 0000000000..235e80d25c --- /dev/null +++ b/packages/react-native-nitro-test-external/nitrogen/generated/shared/c++/OptionalPrimitivesHolder.cpp @@ -0,0 +1,15 @@ +/// +/// OptionalPrimitivesHolder.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "OptionalPrimitivesHolder.hpp" + +namespace margelo::nitro::test::external { + + // Out-of-line defaulted comparison (C++20) — see OptionalPrimitivesHolder.hpp. + bool operator==(const OptionalPrimitivesHolder& lhs, const OptionalPrimitivesHolder& rhs) = default; + +} // namespace margelo::nitro::test::external diff --git a/packages/react-native-nitro-test-external/nitrogen/generated/shared/c++/OptionalPrimitivesHolder.hpp b/packages/react-native-nitro-test-external/nitrogen/generated/shared/c++/OptionalPrimitivesHolder.hpp index 8175fa6d1d..bb5c5d13e3 100644 --- a/packages/react-native-nitro-test-external/nitrogen/generated/shared/c++/OptionalPrimitivesHolder.hpp +++ b/packages/react-native-nitro-test-external/nitrogen/generated/shared/c++/OptionalPrimitivesHolder.hpp @@ -49,7 +49,7 @@ namespace margelo::nitro::test::external { explicit OptionalPrimitivesHolder(std::optional optionalNumber, std::optional optionalBoolean, std::optional optionalUInt64, std::optional optionalInt64): optionalNumber(optionalNumber), optionalBoolean(optionalBoolean), optionalUInt64(optionalUInt64), optionalInt64(optionalInt64) {} public: - friend bool operator==(const OptionalPrimitivesHolder& lhs, const OptionalPrimitivesHolder& rhs) = default; + friend bool operator==(const OptionalPrimitivesHolder& lhs, const OptionalPrimitivesHolder& rhs); }; } // namespace margelo::nitro::test::external diff --git a/packages/react-native-nitro-test-external/package.json b/packages/react-native-nitro-test-external/package.json index 62b20cc11a..fffd3fdaa7 100644 --- a/packages/react-native-nitro-test-external/package.json +++ b/packages/react-native-nitro-test-external/package.json @@ -25,7 +25,8 @@ "app.plugin.js", "nitro.json", "*.podspec", - "README.md" + "README.md", + "Package.swift" ], "scripts": { "typecheck": "tsc --noEmit", @@ -53,7 +54,7 @@ "registry": "https://registry.npmjs.org/" }, "devDependencies": { - "@react-native/eslint-config": "0.85.3", + "@react-native/eslint-config": "0.87.0", "@types/jest": "^29.5.12", "@types/react": "^19.2.15", "eslint": "^9.39.4", @@ -62,7 +63,7 @@ "nitrogen": "*", "prettier": "^3.8.3", "react": "19.2.3", - "react-native": "0.85.3", + "react-native": "0.87.0", "react-native-nitro-modules": "*", "typescript": "^6.0.3" }, diff --git a/packages/react-native-nitro-test/Package.swift b/packages/react-native-nitro-test/Package.swift new file mode 100644 index 0000000000..5fdd6a2451 --- /dev/null +++ b/packages/react-native-nitro-test/Package.swift @@ -0,0 +1,139 @@ +// swift-tools-version: 6.0 +/// +/// Package.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// +// +// React Native SwiftPM manifest for NitroTest. Consumed by RN's +// SwiftPM autolinking as a "self-managed" package through the +// /ios/build/generated/autolinking/libs/NitroTest/ symlink — the +// relative package paths below resolve against that symlink location: +// ../../../../xcframeworks -> /ios/build/xcframeworks (ReactNative) +// ../NitroModules -> libs/NitroModules (nitro core) +// +// NOTE: these path references assume React Native SwiftPM's default +// local-artifacts mode (xcframeworks downloaded into /ios/build/). +// RN's experimental remote-package mode (RN_SPM_REMOTE_URL) is not yet +// supported by generated Nitro manifests. +// + +import PackageDescription + +let reactCxxDefines: [CXXSetting] = [ + // Match the prebuilt React.framework's config-gated C++ ABI. + .define("DEBUG", .when(configuration: .debug)), + .define("NDEBUG", .when(configuration: .release)), + // React Native's prebuilt SwiftPM distribution is New Architecture only. + .define("RCT_NEW_ARCH_ENABLED", to: "1"), +] + +let headerDirs: [CXXSetting] = [ + .headerSearchPath("cpp"), + .headerSearchPath("nitrogen/generated/shared/c++"), + .headerSearchPath("nitrogen/generated/shared/c++/views"), + .headerSearchPath("nitrogen/generated/ios"), + .headerSearchPath("nitrogen/generated/ios/c++"), + .headerSearchPath("nitrogen/generated/ios/c++/views"), +] + +let package = Package( + name: "ReactNativeNitroTest", + platforms: [.iOS(.v15), .visionOS(.v1)], + products: [ + // Only the root of the target chain is vended - SwiftPM pulls + // NitroTest (Swift) and NitroTestCore (C++) in as its target + // dependencies, propagating their module, public headers and objects + // to dependents. Naming them here would add nothing. + .library(name: "ReactNativeNitroTest", targets: ["NitroTestSwiftBridge"]), + ], + dependencies: [ + .package(name: "ReactNative", path: "../../../../xcframeworks"), + .package(name: "NitroModules", path: "../NitroModules"), + .package(name: "ReactNativeNitroTestExternal", path: "../ReactNativeNitroTestExternal"), + ], + targets: [ + // C++ layer: nitrogen's cross-platform specs + the user's C++ hybrids. + // No Swift dependency — this is what the Swift target imports. + .target( + name: "NitroTestCore", + dependencies: [ + .product(name: "NitroModules", package: "NitroModules"), + .product(name: "ReactNativeNitroTestExternal", package: "ReactNativeNitroTestExternal"), + .product(name: "ReactHeaders", package: "ReactNative"), + .product(name: "ReactNativeHeaders", package: "ReactNative"), + .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), + ], + path: ".", + sources: [ + "nitrogen/generated/shared/c++", + ], + publicHeadersPath: "nitrogen/generated/include", + cxxSettings: headerDirs + reactCxxDefines + ), + // Swift layer: nitrogen's Swift specs/_cxx bridges + user Swift hybrids. + .target( + name: "NitroTest", + dependencies: [ + "NitroTestCore", + .product(name: "NitroModules", package: "NitroModules"), + .product(name: "ReactNativeNitroTestExternal", package: "ReactNativeNitroTestExternal"), + ], + path: ".", + sources: [ + "ios/HybridBase.swift", + "ios/HybridChild.swift", + "ios/HybridPlatformObject.swift", + "ios/HybridRecyclableTestView.swift", + "ios/HybridSomeInternalObject.swift", + "ios/HybridTestObjectSwift.swift", + "ios/HybridTestView.swift", + "nitrogen/generated/ios/swift", + "nitrogen/generated/ios/NitroTestAutolinking.swift", + ], + swiftSettings: [ + .interoperabilityMode(.Cxx), + .swiftLanguageMode(.v5), + ] + ), + // SwiftBridge layer: every C++/ObjC++ TU that consumes the Swift-generated + // interface header (NitroTest-Swift.h). Structurally required: + // these TUs cannot live in NitroTestCore (Core -> Swift -> Core + // cycle) nor in the Swift target (SwiftPM forbids mixed languages). + .target( + name: "NitroTestSwiftBridge", + dependencies: [ + "NitroTest", + "NitroTestCore", + .product(name: "NitroModules", package: "NitroModules"), + .product(name: "ReactNativeNitroTestExternal", package: "ReactNativeNitroTestExternal"), + .product(name: "ReactHeaders", package: "ReactNative"), + .product(name: "ReactNativeHeaders", package: "ReactNative"), + .product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"), + ], + path: ".", + sources: [ + "cpp/HybridTestObjectCpp.cpp", + "nitrogen/generated/ios/c++", + "nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.cpp", + "nitrogen/generated/ios/NitroTestAutolinking.mm", + ], + publicHeadersPath: "nitrogen/generated/include-bridge", + cxxSettings: headerDirs + reactCxxDefines + [ + // Under Xcode, React Native's SPM sync symlinks + // $OBJROOT/GeneratedModuleMaps- (where Xcode stages + // the Swift-generated NitroTest-Swift.h) to this stable + // path, so the Swift-Cxx umbrella can include it TEXTUALLY. + // Under `swift build` SwiftPM vends the header natively and + // this path simply doesn't exist. + .headerSearchPath(".spm-derived-headers"), + ], + linkerSettings: [ + .linkedFramework("Foundation"), + .linkedFramework("UIKit", .when(platforms: [.iOS])), + ] + ), + ], + cxxLanguageStandard: .cxx20 +) diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake index 222b2ef3fe..8ecdafe14f 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake +++ b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake @@ -35,10 +35,18 @@ target_sources( # Shared Nitrogen C++ sources ../nitrogen/generated/shared/c++/HybridBaseSpec.cpp ../nitrogen/generated/shared/c++/HybridChildSpec.cpp + ../nitrogen/generated/shared/c++/Person.cpp + ../nitrogen/generated/shared/c++/Car.cpp ../nitrogen/generated/shared/c++/HybridPlatformObjectSpec.cpp ../nitrogen/generated/shared/c++/HybridRecyclableTestViewSpec.cpp ../nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp ../nitrogen/generated/shared/c++/HybridTestObjectCppSpec.cpp + ../nitrogen/generated/shared/c++/PartialPerson.cpp + ../nitrogen/generated/shared/c++/SecondMapWrapper.cpp + ../nitrogen/generated/shared/c++/MapWrapper.cpp + ../nitrogen/generated/shared/c++/OptionalWrapper.cpp + ../nitrogen/generated/shared/c++/OptionalEnumWrapper.cpp + ../nitrogen/generated/shared/c++/ExternalObjectStruct.cpp ../nitrogen/generated/shared/c++/HybridTestObjectSwiftKotlinSpec.cpp ../nitrogen/generated/shared/c++/HybridTestViewSpec.cpp ../nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Umbrella.hpp b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Umbrella.hpp index 71580d7e9c..3d2e9ac128 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Umbrella.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Umbrella.hpp @@ -131,5 +131,21 @@ namespace NitroTest { class HybridTestViewSpec_cxx; } #elif __has_include() #include #else +// React Native SwiftPM (Preview): Xcode stages the generated header in +// DerivedData ($OBJROOT/GeneratedModuleMaps-/). Nitro's RN SwiftPM +// autolinking plugin symlinks $OBJROOT to `.spm-derived-headers` inside this +// package (a headerSearchPath of the glue target), and TargetConditionals +// picks the platform subdirectory. +#include +#if TARGET_OS_VISION && TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-xrsimulator/NitroTest-Swift.h") +#include "GeneratedModuleMaps-xrsimulator/NitroTest-Swift.h" +#elif TARGET_OS_VISION && __has_include("GeneratedModuleMaps-xros/NitroTest-Swift.h") +#include "GeneratedModuleMaps-xros/NitroTest-Swift.h" +#elif TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-iphonesimulator/NitroTest-Swift.h") +#include "GeneratedModuleMaps-iphonesimulator/NitroTest-Swift.h" +#elif !TARGET_OS_SIMULATOR && __has_include("GeneratedModuleMaps-iphoneos/NitroTest-Swift.h") +#include "GeneratedModuleMaps-iphoneos/NitroTest-Swift.h" +#else #error NitroTest's autogenerated Swift header cannot be found! Make sure the Swift module's name (= podspec name) is actually "NitroTest", and try building the app first. #endif +#endif diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/NitroTestExports.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/NitroTestExports.swift new file mode 100644 index 0000000000..5591c366f6 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/NitroTestExports.swift @@ -0,0 +1,17 @@ +/// +/// NitroTestExports.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +// Re-exports this module's C++ half (the NitroTestCore clang target) so +// that every OTHER Swift file in this module sees nitrogen's generated C++ +// specs WITHOUT importing it itself — `@_exported import` is visible module- +// wide, not just in this file. +// +// Under CocoaPods the module is a single mixed target, so no NitroTestCore +// module exists and this file compiles to nothing. +#if canImport(NitroTestCore) +@_exported import NitroTestCore +#endif diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Car.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Car.cpp new file mode 100644 index 0000000000..efa7899881 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Car.cpp @@ -0,0 +1,15 @@ +/// +/// Car.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "Car.hpp" + +namespace margelo::nitro::test { + + // Out-of-line defaulted comparison (C++20) — see Car.hpp. + bool operator==(const Car& lhs, const Car& rhs) = default; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Car.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Car.hpp index 471e4e6a0b..4df1217fb5 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Car.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Car.hpp @@ -64,7 +64,7 @@ namespace margelo::nitro::test { explicit Car(double year, std::string make, std::string model, double power, Powertrain powertrain, std::optional driver, std::vector passengers, std::optional isFast, std::optional favouriteTrack, std::vector performanceScores, std::optional> someVariant): year(year), make(make), model(model), power(power), powertrain(powertrain), driver(driver), passengers(passengers), isFast(isFast), favouriteTrack(favouriteTrack), performanceScores(performanceScores), someVariant(someVariant) {} public: - friend bool operator==(const Car& lhs, const Car& rhs) = default; + friend bool operator==(const Car& lhs, const Car& rhs); }; } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/ExternalObjectStruct.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/ExternalObjectStruct.cpp new file mode 100644 index 0000000000..1e086ed667 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/ExternalObjectStruct.cpp @@ -0,0 +1,15 @@ +/// +/// ExternalObjectStruct.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "ExternalObjectStruct.hpp" + +namespace margelo::nitro::test { + + // Out-of-line defaulted comparison (C++20) — see ExternalObjectStruct.hpp. + bool operator==(const ExternalObjectStruct& lhs, const ExternalObjectStruct& rhs) = default; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/ExternalObjectStruct.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/ExternalObjectStruct.hpp index 1be39830d2..8a07308441 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/ExternalObjectStruct.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/ExternalObjectStruct.hpp @@ -48,7 +48,7 @@ namespace margelo::nitro::test { explicit ExternalObjectStruct(std::shared_ptr someExternal): someExternal(someExternal) {} public: - friend bool operator==(const ExternalObjectStruct& lhs, const ExternalObjectStruct& rhs) = default; + friend bool operator==(const ExternalObjectStruct& lhs, const ExternalObjectStruct& rhs); }; } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/MapWrapper.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/MapWrapper.cpp new file mode 100644 index 0000000000..b7365890ef --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/MapWrapper.cpp @@ -0,0 +1,15 @@ +/// +/// MapWrapper.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "MapWrapper.hpp" + +namespace margelo::nitro::test { + + // Out-of-line defaulted comparison (C++20) — see MapWrapper.hpp. + bool operator==(const MapWrapper& lhs, const MapWrapper& rhs) = default; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/MapWrapper.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/MapWrapper.hpp index 5d2e05460f..76b5781897 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/MapWrapper.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/MapWrapper.hpp @@ -50,7 +50,7 @@ namespace margelo::nitro::test { explicit MapWrapper(std::unordered_map map, SecondMapWrapper secondMap): map(map), secondMap(secondMap) {} public: - friend bool operator==(const MapWrapper& lhs, const MapWrapper& rhs) = default; + friend bool operator==(const MapWrapper& lhs, const MapWrapper& rhs); }; } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalEnumWrapper.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalEnumWrapper.cpp new file mode 100644 index 0000000000..ff6592b549 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalEnumWrapper.cpp @@ -0,0 +1,15 @@ +/// +/// OptionalEnumWrapper.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "OptionalEnumWrapper.hpp" + +namespace margelo::nitro::test { + + // Out-of-line defaulted comparison (C++20) — see OptionalEnumWrapper.hpp. + bool operator==(const OptionalEnumWrapper& lhs, const OptionalEnumWrapper& rhs) = default; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalEnumWrapper.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalEnumWrapper.hpp index 2627909afc..5022f8e0d8 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalEnumWrapper.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalEnumWrapper.hpp @@ -65,7 +65,7 @@ namespace margelo::nitro::test { explicit OptionalEnumWrapper(std::optional count, std::optional weight, std::optional ttl, std::optional jitter, std::optional retries, std::optional delayMs, std::optional timeoutMs, std::optional ratio, std::optional threshold, std::optional enabled, std::optional active, std::optional shouldBuffer, std::optional shouldRetry, std::optional verbose, std::optional tier, std::optional region, std::optional stage, std::optional tone): count(count), weight(weight), ttl(ttl), jitter(jitter), retries(retries), delayMs(delayMs), timeoutMs(timeoutMs), ratio(ratio), threshold(threshold), enabled(enabled), active(active), shouldBuffer(shouldBuffer), shouldRetry(shouldRetry), verbose(verbose), tier(tier), region(region), stage(stage), tone(tone) {} public: - friend bool operator==(const OptionalEnumWrapper& lhs, const OptionalEnumWrapper& rhs) = default; + friend bool operator==(const OptionalEnumWrapper& lhs, const OptionalEnumWrapper& rhs); }; } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalWrapper.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalWrapper.cpp new file mode 100644 index 0000000000..502e4eeddc --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalWrapper.cpp @@ -0,0 +1,15 @@ +/// +/// OptionalWrapper.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "OptionalWrapper.hpp" + +namespace margelo::nitro::test { + + // Out-of-line defaulted comparison (C++20) — see OptionalWrapper.hpp. + bool operator==(const OptionalWrapper& lhs, const OptionalWrapper& rhs) = default; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalWrapper.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalWrapper.hpp index 2f6de6bcd7..40dc809a29 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalWrapper.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/OptionalWrapper.hpp @@ -49,7 +49,7 @@ namespace margelo::nitro::test { explicit OptionalWrapper(std::optional> optionalArrayBuffer, std::optional optionalString): optionalArrayBuffer(optionalArrayBuffer), optionalString(optionalString) {} public: - friend bool operator==(const OptionalWrapper& lhs, const OptionalWrapper& rhs) = default; + friend bool operator==(const OptionalWrapper& lhs, const OptionalWrapper& rhs); }; } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/PartialPerson.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/PartialPerson.cpp new file mode 100644 index 0000000000..c7a58adbde --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/PartialPerson.cpp @@ -0,0 +1,15 @@ +/// +/// PartialPerson.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "PartialPerson.hpp" + +namespace margelo::nitro::test { + + // Out-of-line defaulted comparison (C++20) — see PartialPerson.hpp. + bool operator==(const PartialPerson& lhs, const PartialPerson& rhs) = default; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/PartialPerson.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/PartialPerson.hpp index f3e43a0fd5..7b7e6d24d7 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/PartialPerson.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/PartialPerson.hpp @@ -48,7 +48,7 @@ namespace margelo::nitro::test { explicit PartialPerson(std::optional name, std::optional age): name(name), age(age) {} public: - friend bool operator==(const PartialPerson& lhs, const PartialPerson& rhs) = default; + friend bool operator==(const PartialPerson& lhs, const PartialPerson& rhs); }; } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Person.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Person.cpp new file mode 100644 index 0000000000..ee2879dd5f --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Person.cpp @@ -0,0 +1,15 @@ +/// +/// Person.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "Person.hpp" + +namespace margelo::nitro::test { + + // Out-of-line defaulted comparison (C++20) — see Person.hpp. + bool operator==(const Person& lhs, const Person& rhs) = default; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Person.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Person.hpp index 700dd35489..c39d402230 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Person.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/Person.hpp @@ -47,7 +47,7 @@ namespace margelo::nitro::test { explicit Person(std::string name, double age): name(name), age(age) {} public: - friend bool operator==(const Person& lhs, const Person& rhs) = default; + friend bool operator==(const Person& lhs, const Person& rhs); }; } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/SecondMapWrapper.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/SecondMapWrapper.cpp new file mode 100644 index 0000000000..7b4d0c881b --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/SecondMapWrapper.cpp @@ -0,0 +1,15 @@ +/// +/// SecondMapWrapper.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "SecondMapWrapper.hpp" + +namespace margelo::nitro::test { + + // Out-of-line defaulted comparison (C++20) — see SecondMapWrapper.hpp. + bool operator==(const SecondMapWrapper& lhs, const SecondMapWrapper& rhs) = default; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/SecondMapWrapper.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/SecondMapWrapper.hpp index 3444b0302e..16f7d177a7 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/SecondMapWrapper.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/SecondMapWrapper.hpp @@ -47,7 +47,7 @@ namespace margelo::nitro::test { explicit SecondMapWrapper(std::unordered_map second): second(second) {} public: - friend bool operator==(const SecondMapWrapper& lhs, const SecondMapWrapper& rhs) = default; + friend bool operator==(const SecondMapWrapper& lhs, const SecondMapWrapper& rhs); }; } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/package.json b/packages/react-native-nitro-test/package.json index da802003c5..4477c6e362 100644 --- a/packages/react-native-nitro-test/package.json +++ b/packages/react-native-nitro-test/package.json @@ -22,7 +22,9 @@ "ios/**/*.swift", "app.plugin.js", "*.podspec", - "README.md" + "README.md", + "Package.swift", + "nitrogen/" ], "keywords": [ "react-native", @@ -60,9 +62,9 @@ "@types/react": "^19.2.15", "jest": "*", "nitrogen": "*", - "@react-native/jest-preset": "0.85.3", + "@react-native/jest-preset": "0.87.0", "react": "19.2.3", - "react-native": "0.85.3", + "react-native": "0.87.0", "react-native-nitro-modules": "*", "react-native-nitro-test-external": "*" }, diff --git a/packages/template/package.json b/packages/template/package.json index 5a8c98ec8c..73e0444bc0 100644 --- a/packages/template/package.json +++ b/packages/template/package.json @@ -54,7 +54,7 @@ "registry": "https://registry.npmjs.org/" }, "devDependencies": { - "@react-native/eslint-config": "0.85.3", + "@react-native/eslint-config": "0.87.0", "@types/react": "^19.2.15", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", @@ -62,7 +62,7 @@ "nitrogen": "*", "prettier": "^3.8.3", "react": "19.2.3", - "react-native": "0.85.3", + "react-native": "0.87.0", "react-native-nitro-modules": "*", "typescript": "^6.0.3" },