@@ -7,15 +7,16 @@ import * as xcode from "@bacons/xcode";
77import * as xcodeJson from "@bacons/xcode/json" ;
88import * as zod from "zod" ;
99
10- import { chalk , spawn } from "@react-native-node-api/cli-utils" ;
10+ import { assertFixable , chalk , spawn } from "@react-native-node-api/cli-utils" ;
1111
1212import { getLatestMtime , getLibraryName } from "../path-utils.js" ;
1313import {
1414 getLinkedModuleOutputPath ,
1515 LinkModuleOptions ,
1616 LinkModuleResult ,
17+ ModuleLinker ,
1718} from "./link-modules.js" ;
18- import { findXcodeProject } from "./xcode-helpers.js" ;
19+ import { findXcodeProject , getBuildDirPath } from "./xcode-helpers.js" ;
1920
2021const PACKAGE_ROOT = path . resolve ( __dirname , ".." , ".." , ".." ) ;
2122const CLI_PATH = path . resolve ( PACKAGE_ROOT , "bin" , "react-native-node-api.mjs" ) ;
@@ -47,6 +48,8 @@ export async function ensureXcodeBuildPhase(fromPath: string) {
4748 existingBuildPhase . removeFromProject ( ) ;
4849 }
4950
51+ // TODO: Declare input and output files to prevent unnecessary runs
52+
5053 mainTarget . createBuildPhase ( xcode . PBXShellScriptBuildPhase , {
5154 name : BUILD_PHASE_NAME ,
5255 shellScript : [
@@ -108,6 +111,9 @@ const XcframeworkInfoSchema = zod.looseObject({
108111 LibraryIdentifier : zod . string ( ) ,
109112 LibraryPath : zod . string ( ) ,
110113 DebugSymbolsPath : zod . string ( ) . optional ( ) ,
114+ SupportedArchitectures : zod . array ( zod . string ( ) ) ,
115+ SupportedPlatform : zod . string ( ) ,
116+ SupportedPlatformVariant : zod . string ( ) . optional ( ) ,
111117 } ) ,
112118 ) ,
113119 CFBundlePackageType : zod . literal ( "XFWK" ) ,
@@ -376,84 +382,190 @@ export async function linkVersionedFramework({
376382 ) ;
377383}
378384
385+ export async function createAppleLinker ( ) : Promise < ModuleLinker > {
386+ assert . equal (
387+ process . platform ,
388+ "darwin" ,
389+ "Linking Apple addons are only supported on macOS" ,
390+ ) ;
391+
392+ const {
393+ TARGET_BUILD_DIR : targetBuildDir ,
394+ FRAMEWORKS_FOLDER_PATH : frameworksFolderPath ,
395+ } = process . env ;
396+ assert ( targetBuildDir , "Expected TARGET_BUILD_DIR to be set by Xcodebuild" ) ;
397+ assert (
398+ frameworksFolderPath ,
399+ "Expected FRAMEWORKS_FOLDER_PATH to be set by Xcodebuild" ,
400+ ) ;
401+
402+ const outputPath = path . join ( targetBuildDir , frameworksFolderPath ) ;
403+ await fs . promises . mkdir ( outputPath , { recursive : true } ) ;
404+
405+ const {
406+ EXPANDED_CODE_SIGN_IDENTITY : signingIdentity = "-" ,
407+ CODE_SIGNING_REQUIRED : signingRequired ,
408+ CODE_SIGNING_ALLOWED : signingAllowed ,
409+ } = process . env ;
410+
411+ return ( options : LinkModuleOptions ) => {
412+ return linkXcframework ( {
413+ ...options ,
414+ outputPath,
415+ signingIdentity :
416+ signingRequired !== "NO" && signingAllowed !== "NO"
417+ ? signingIdentity
418+ : undefined ,
419+ } ) ;
420+ } ;
421+ }
422+
423+ export function determineFrameworkSlice ( ) : {
424+ platform : string ;
425+ platformVariant ?: string ;
426+ architectures : string [ ] ;
427+ } {
428+ const {
429+ PLATFORM_NAME : platformName ,
430+ EFFECTIVE_PLATFORM_NAME : effectivePlatformName ,
431+ ARCHS : architecturesJoined ,
432+ } = process . env ;
433+
434+ assert ( platformName , "Expected PLATFORM_NAME to be set by Xcodebuild" ) ;
435+ assert ( architecturesJoined , "Expected ARCHS to be set by Xcodebuild" ) ;
436+ const architectures = architecturesJoined . split ( " " ) ;
437+
438+ const simulator = platformName . endsWith ( "simulator" ) ;
439+
440+ if ( platformName === "iphonesimulator" ) {
441+ return {
442+ platform : "ios" ,
443+ platformVariant : simulator ? "simulator" : undefined ,
444+ architectures,
445+ } ;
446+ } else if ( platformName === "macosx" ) {
447+ return {
448+ platform : "macos" ,
449+ architectures,
450+ platformVariant : effectivePlatformName ?. endsWith ( "maccatalyst" )
451+ ? "maccatalyst"
452+ : undefined ,
453+ } ;
454+ }
455+
456+ throw new Error (
457+ `Unsupported platform: ${ effectivePlatformName ?? platformName } ` ,
458+ ) ;
459+ }
460+
379461export async function linkXcframework ( {
380462 platform,
381463 modulePath,
382464 incremental,
383465 naming,
384- } : LinkModuleOptions ) : Promise < LinkModuleResult > {
385- assert . equal (
386- process . platform ,
387- "darwin" ,
388- "Linking Apple addons are only supported on macOS" ,
466+ outputPath : outputParentPath ,
467+ signingIdentity,
468+ } : LinkModuleOptions & {
469+ outputPath : string ;
470+ signingIdentity ?: string ;
471+ } ) : Promise < LinkModuleResult > {
472+ assertFixable (
473+ ! incremental ,
474+ "Incremental linking is not supported for Apple frameworks" ,
475+ {
476+ instructions : "Run the command with the --force flag" ,
477+ } ,
389478 ) ;
390479 // Copy the xcframework to the output directory and rename the framework and binary
391480 const newLibraryName = getLibraryName ( modulePath , naming ) ;
392- const outputPath = getLinkedModuleOutputPath ( platform , modulePath , naming ) ;
393-
394- if ( incremental && fs . existsSync ( outputPath ) ) {
395- const moduleModified = getLatestMtime ( modulePath ) ;
396- const outputModified = getLatestMtime ( outputPath ) ;
397- if ( moduleModified < outputModified ) {
398- return {
399- originalPath : modulePath ,
400- libraryName : newLibraryName ,
401- outputPath,
402- skipped : true ,
403- } ;
404- }
405- }
481+ const frameworkOutputPath = path . join (
482+ outputParentPath ,
483+ `${ newLibraryName } .framework` ,
484+ ) ;
406485 // Delete any existing xcframework (or xcodebuild will try to amend it)
407- await fs . promises . rm ( outputPath , { recursive : true , force : true } ) ;
408- // Copy the existing xcframework to the output path
409- await fs . promises . cp ( modulePath , outputPath , {
410- recursive : true ,
411- verbatimSymlinks : true ,
412- } ) ;
486+ await fs . promises . rm ( frameworkOutputPath , { recursive : true , force : true } ) ;
413487
414- const info = await readXcframeworkInfo ( path . join ( outputPath , "Info.plist" ) ) ;
488+ const info = await readXcframeworkInfo ( path . join ( modulePath , "Info.plist" ) ) ;
415489
416- await Promise . all (
417- info . AvailableLibraries . map ( async ( framework ) => {
418- const frameworkPath = path . join (
419- outputPath ,
420- framework . LibraryIdentifier ,
421- framework . LibraryPath ,
422- ) ;
423- await linkFramework ( {
424- frameworkPath,
425- newLibraryName,
426- debugSymbolsPath : framework . DebugSymbolsPath
427- ? path . join (
428- outputPath ,
429- framework . LibraryIdentifier ,
430- framework . DebugSymbolsPath ,
431- )
432- : undefined ,
433- } ) ;
434- } ) ,
490+ // TODO: Assert the existence of environment variables injected by Xcodebuild
491+ // TODO: Pick and assert the existence of the right framework slice based on the environment variables
492+ // TODO: Link the framework into the output path
493+
494+ const expectedSlice = determineFrameworkSlice ( ) ;
495+
496+ const framework = info . AvailableLibraries . find ( ( framework ) => {
497+ return (
498+ expectedSlice . platform === framework . SupportedPlatform &&
499+ expectedSlice . platformVariant === framework . SupportedPlatformVariant &&
500+ expectedSlice . architectures . every ( ( architecture ) =>
501+ framework . SupportedArchitectures . includes ( architecture ) ,
502+ )
503+ ) ;
504+ } ) ;
505+ assert (
506+ framework ,
507+ `Failed to find a framework slice matching: ${ JSON . stringify ( expectedSlice ) } ` ,
435508 ) ;
436509
437- await writeXcframeworkInfo ( outputPath , {
438- ...info ,
439- AvailableLibraries : info . AvailableLibraries . map ( ( library ) => {
440- return {
441- ...library ,
442- LibraryPath : `${ newLibraryName } .framework` ,
443- BinaryPath : `${ newLibraryName } .framework/${ newLibraryName } ` ,
444- } ;
445- } ) ,
510+ const originalFrameworkPath = path . join (
511+ modulePath ,
512+ framework . LibraryIdentifier ,
513+ framework . LibraryPath ,
514+ ) ;
515+
516+ // Copy the existing framework to the output path
517+ await fs . promises . cp ( originalFrameworkPath , frameworkOutputPath , {
518+ recursive : true ,
519+ verbatimSymlinks : true ,
446520 } ) ;
447521
448- // Delete any leftover "magic file"
449- await fs . promises . rm ( path . join ( outputPath , "react-native-node-api-module" ) , {
450- force : true ,
522+ await linkFramework ( {
523+ frameworkPath : frameworkOutputPath ,
524+ newLibraryName,
525+ debugSymbolsPath : framework . DebugSymbolsPath
526+ ? path . join (
527+ modulePath ,
528+ framework . LibraryIdentifier ,
529+ framework . DebugSymbolsPath ,
530+ )
531+ : undefined ,
451532 } ) ;
452533
534+ if ( signingIdentity ) {
535+ await signFramework ( {
536+ frameworkPath : frameworkOutputPath ,
537+ identity : signingIdentity ,
538+ } ) ;
539+ }
540+
453541 return {
454542 originalPath : modulePath ,
455543 libraryName : newLibraryName ,
456- outputPath,
544+ outputPath : frameworkOutputPath ,
457545 skipped : false ,
546+ signed : signingIdentity ? true : false ,
458547 } ;
459548}
549+
550+ export async function signFramework ( {
551+ frameworkPath,
552+ identity,
553+ } : {
554+ frameworkPath : string ;
555+ identity : string ;
556+ } ) {
557+ await spawn (
558+ "codesign" ,
559+ [
560+ "--force" ,
561+ "--sign" ,
562+ identity ,
563+ "--timestamp=none" ,
564+ "--preserve-metadata=identifier,entitlements,flags" ,
565+ frameworkPath ,
566+ ] ,
567+ {
568+ outputMode : "buffered" ,
569+ } ,
570+ ) ;
571+ }
0 commit comments