Search code examples
ioscocoapodsgodotscons

How to compile iOS Cocoapods dependency using Scons?


I am trying to create an iOS plugin for Godot Game Engine. Now my plugin requires integration with FirebaseAuth so I created a Podfile and added those dependencies. Here is the complete source code

This is my SConstruct file

#!/usr/bin/env python
import os
import sys
import subprocess

if sys.version_info < (3,):
    def decode_utf8(x):
        return x
else:
    import codecs
    def decode_utf8(x):
        return codecs.utf_8_decode(x)[0]

# Most of the settings are taken from https://github.com/BastiaanOlij/gdnative_cpp_example

opts = Variables([], ARGUMENTS)

# Gets the standard flags CC, CCX, etc.
env = DefaultEnvironment()

# Define our options
opts.Add(EnumVariable('target', "Compilation target", 'debug', ['debug', 'release', "release_debug"]))
opts.Add(EnumVariable('arch', "Compilation Architecture", '', ['', 'arm64', 'armv7', 'x86_64']))
opts.Add(BoolVariable('simulator', "Compilation platform", 'no'))
opts.Add(BoolVariable('use_llvm', "Use the LLVM / Clang compiler", 'no'))
opts.Add(PathVariable('target_path', 'The path where the lib is installed.', 'bin/'))
opts.Add(EnumVariable('plugin', 'Plugin to build', '', ['', 'arithematic']))
opts.Add(EnumVariable('version', 'Godot version to target', '', ['', '3.x', '4.0']))

# Updates the environment with the option variables.
opts.Update(env)

# Process some arguments
if env['use_llvm']:
    env['CC'] = 'clang'
    env['CXX'] = 'clang++'

if env['arch'] == '':
    print("No valid arch selected.")
    quit();

if env['plugin'] == '':
    print("No valid plugin selected.")
    quit();

if env['version'] == '':
    print("No valid Godot version selected.")
    quit();

# For the reference:
# - CCFLAGS are compilation flags shared between C and C++
# - CFLAGS are for C-specific compilation flags
# - CXXFLAGS are for C++-specific compilation flags
# - CPPFLAGS are for pre-processor flags
# - CPPDEFINES are for pre-processor defines
# - LINKFLAGS are for linking flags

# Enable Obj-C modules
env.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])

if env['simulator']:
    sdk_name = 'iphonesimulator'
    env.Append(CCFLAGS=['-mios-simulator-version-min=10.0'])
    env.Append(LINKFLAGS=["-mios-simulator-version-min=10.0"])
else:
    sdk_name = 'iphoneos'
    env.Append(CCFLAGS=['-miphoneos-version-min=10.0'])
    env.Append(LINKFLAGS=["-miphoneos-version-min=10.0"])

try:
    sdk_path = decode_utf8(subprocess.check_output(['xcrun', '--sdk', sdk_name, '--show-sdk-path']).strip())
except (subprocess.CalledProcessError, OSError):
    raise ValueError("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))

env.Append(CCFLAGS=[
    '-fobjc-arc', 
    '-fmessage-length=0', '-fno-strict-aliasing', '-fdiagnostics-print-source-range-info', 
    '-fdiagnostics-show-category=id', '-fdiagnostics-parseable-fixits', '-fpascal-strings', 
    '-fblocks', '-fvisibility=hidden', '-MMD', '-MT', 'dependencies', '-fno-exceptions', 
    '-Wno-ambiguous-macro', 
    '-Wall', '-Werror=return-type',
    # '-Wextra',
])

env.Append(CCFLAGS=['-arch', env['arch'], "-isysroot", "$IOS_SDK_PATH", "-stdlib=libc++", '-isysroot', sdk_path])
env.Append(CCFLAGS=['-DPTRCALL_ENABLED'])
env.Prepend(CXXFLAGS=[
    '-DNEED_LONG_INT', '-DLIBYUV_DISABLE_NEON', 
    '-DIOS_ENABLED', '-DUNIX_ENABLED', '-DCOREAUDIO_ENABLED'
])
env.Append(LINKFLAGS=["-arch", env['arch'], '-isysroot', sdk_path, '-F' + sdk_path])

if env['arch'] == 'armv7':
    env.Prepend(CXXFLAGS=['-fno-aligned-allocation'])

if env['version'] == '3.x':
    env.Append(CCFLAGS=["$IPHONESDK"])
    env.Prepend(CXXFLAGS=['-DIPHONE_ENABLED'])
    env.Prepend(CXXFLAGS=['-DVERSION_3_X'])

    env.Prepend(CFLAGS=['-std=gnu11'])
    env.Prepend(CXXFLAGS=['-DGLES_ENABLED', '-std=gnu++14'])

    if env['target'] == 'debug':
        env.Prepend(CXXFLAGS=[
            '-gdwarf-2', '-O0', 
            '-DDEBUG_MEMORY_ALLOC', '-DDISABLE_FORCED_INLINE', 
            '-D_DEBUG', '-DDEBUG=1', '-DDEBUG_ENABLED',
            '-DPTRCALL_ENABLED',
        ])
    elif env['target'] == 'release_debug':
        env.Prepend(CXXFLAGS=['-O2', '-ftree-vectorize',
            '-DNDEBUG', '-DNS_BLOCK_ASSERTIONS=1', '-DDEBUG_ENABLED', 
            '-DPTRCALL_ENABLED',
        ])

        if env['arch'] != 'armv7':
            env.Prepend(CXXFLAGS=['-fomit-frame-pointer'])
    else:
        env.Prepend(CXXFLAGS=[
            '-O2', '-ftree-vectorize',
            '-DNDEBUG', '-DNS_BLOCK_ASSERTIONS=1', 
            '-DPTRCALL_ENABLED',
        ])

        if env['arch'] != 'armv7':
            env.Prepend(CXXFLAGS=['-fomit-frame-pointer'])
elif env['version'] == '4.0':
    env.Append(CCFLAGS=["$IOS_SDK_PATH"])
    env.Prepend(CXXFLAGS=['-DIOS_ENABLED'])
    env.Prepend(CXXFLAGS=['-DVERSION_4_0'])

    env.Prepend(CFLAGS=['-std=gnu11'])
    env.Prepend(CXXFLAGS=['-DVULKAN_ENABLED', '-std=gnu++17'])

    if env['target'] == 'debug':
        env.Prepend(CXXFLAGS=[
            '-gdwarf-2', '-O0', 
            '-DDEBUG_MEMORY_ALLOC', '-DDISABLE_FORCED_INLINE', 
            '-D_DEBUG', '-DDEBUG=1', '-DDEBUG_ENABLED', 
        ])
    elif env['target'] == 'release_debug':
        env.Prepend(CXXFLAGS=[
            '-O2', '-ftree-vectorize',
            '-DNDEBUG', '-DNS_BLOCK_ASSERTIONS=1', '-DDEBUG_ENABLED',
        ])

        if env['arch'] != 'armv7':
            env.Prepend(CXXFLAGS=['-fomit-frame-pointer'])
    else:
        env.Prepend(CXXFLAGS=[
            '-O2', '-ftree-vectorize',
            '-DNDEBUG', '-DNS_BLOCK_ASSERTIONS=1',
        ])

        if env['arch'] != 'armv7':
            env.Prepend(CXXFLAGS=['-fomit-frame-pointer'])            
else:
    print("No valid version to set flags for.")
    quit();

# Adding header files
if env['version'] == '3.x':
    env.Append(CPPPATH=[
        '.', 
        'godot', 
        'godot/platform/iphone',
    ])
else:
       env.Append(CPPPATH=[
        '.', 
        'godot', 
        'godot/platform/ios',
    ])
       


sources = Glob('arithematic/*.cpp')

sources.append(Glob('arithematic/*.mm'))
sources.append(Glob('arithematic/*.m'))


library_platform = env["arch"] + "-" + ("simulator" if env["simulator"] else "iphone")
library_name = env['plugin'] + "." + library_platform + "." + env["target"] + ".a"
library = env.StaticLibrary(target=env['target_path'] + library_name, source=sources)

If I run scons target=release_debug arch=arm64 plugin=arithematic version=4.0 I get error saying

In file included from arithematic/arithematic.mm:16:
arithematic/arithematic.h:19:9:{19:9-19:38}: fatal error: 'FirebaseCore/FirebaseCore.h' file not found [1]
 #import <FirebaseCore/FirebaseCore.h>
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
scons: *** [arithematic/arithematic.o] Error 1
scons: building terminated because of errors.
    
    Default(library)
    
    # Generates help for the -h scons option.
    Help(opts.GenerateHelpText(env))

then I added

sources.append(Glob('Pods/FirebaseCore/FirebaseCore/Sources/*.m'))
sources.append(Glob('Pods/FirebaseAuth/FirebaseAuth/Sources/*.m'))

and ran the command again but now I get

Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m:17:9:{17:9-17:59}: fatal error: 'FirebaseCore/Sources/FIRAnalyticsConfiguration.h' file not found [1]
 #import "FirebaseCore/Sources/FIRAnalyticsConfiguration.h"
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
scons: *** [Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.o] Error 1
scons: building terminated because of errors.

Is there a way to include the entire Pods folder as there are multiple sub directories within directories

Also I tried

def collect_sources(directory, extensions):
    sources = []
    for root, _, files in os.walk(directory):
        for file in files:
            if file.endswith(extensions):
                sources.append(os.path.join(root, file))
    return sources

pod_sources = collect_sources('Pods', ('.m', '.mm', '.cpp', '.h'))
sources.extend(pod_sources)

but it gives error saying

Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m:17:9:{17:9-17:59}: fatal error: 'FirebaseCore/Sources/FIRAnalyticsConfiguration.h' file not found [1]
 #import "FirebaseCore/Sources/FIRAnalyticsConfiguration.h"
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
scons: *** [Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.o] Error 1
scons: building terminated because of errors.

EDIT:

Compile CommandLine:

Command:  gcc -o Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.o -c -std=gnu11 -fmodules -fcxx-modules -miphoneos-version-min=10.0 -fobjc-arc -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -fno-exceptions -Wno-ambiguous-macro -Wall -Werror=return-type -arch arm64 -isysroot -stdlib=libc++ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.5.sdk -DPTRCALL_ENABLED -I. -Igodot -Igodot/platform/ios Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m
Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m:17:9:{17:9-17:59}: fatal error: 'FirebaseCore/Sources/FIRAnalyticsConfiguration.h' file not found [1]

EDIT 2:

Include FirebaseCore in include paths

So I added below code to SConstruct

def find_include_dirs(root_dir):
    include_dirs = []
    for dirpath, dirnames, filenames in os.walk(root_dir):
        if any(filename.endswith('.h') for filename in filenames):
            include_dirs.append(dirpath)
    return include_dirs

include_dirs = find_include_dirs('Pods')
env.Append(CPPPATH=include_dirs)

Here is the command line

scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.o -c -std=gnu11 -fmodules -fcxx-modules -miphoneos-version-min=16.0 -fobjc-arc -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -fno-exceptions -Wno-ambiguous-macro -Wall -Werror=return-type -arch arm64 -isysroot -stdlib=libc++ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.5.sdk -DPTRCALL_ENABLED -I. -Igodot -Igodot/platform/ios -IPods/FirebaseCore/FirebaseCore/Extension -IPods/FirebaseCore/FirebaseCore/Sources -IPods/FirebaseCore/FirebaseCore/Sources/Public/FirebaseCore -IPods/RecaptchaInterop/RecaptchaEnterprise/RecaptchaInterop/Public/RecaptchaInterop -IPods/PromisesObjC/Sources/FBLPromises/include "-IPods/Target Support Files/FirebaseCore" "-IPods/Target Support Files/RecaptchaInterop" "-IPods/Target Support Files/PromisesObjC" "-IPods/Target Support Files/FirebaseAuth" "-IPods/Target Support Files/FirebaseAppCheckInterop" "-IPods/Target Support Files/GTMSessionFetcher" "-IPods/Target Support Files/FirebaseCoreInternal" "-IPods/Target Support Files/Pods-arithematic" "-IPods/Target Support Files/GoogleUtilities" -IPods/FirebaseAuth/FirebaseCore/Extension -IPods/FirebaseAuth/FirebaseAuth/Interop -IPods/FirebaseAuth/FirebaseAuth/Sources/Auth -IPods/FirebaseAuth/FirebaseAuth/Sources/SystemService -IPods/FirebaseAuth/FirebaseAuth/Sources/MultiFactor -IPods/FirebaseAuth/FirebaseAuth/Sources/MultiFactor/Phone -IPods/FirebaseAuth/FirebaseAuth/Sources/MultiFactor/TOTP -IPods/FirebaseAuth/FirebaseAuth/Sources/User -IPods/FirebaseAuth/FirebaseAuth/Sources/Backend -IPods/FirebaseAuth/FirebaseAuth/Sources/Backend/RPC -IPods/FirebaseAuth/FirebaseAuth/Sources/Backend/RPC/Proto -IPods/FirebaseAuth/FirebaseAuth/Sources/Backend/RPC/Proto/Phone -IPods/FirebaseAuth/FirebaseAuth/Sources/Backend/RPC/Proto/TOTP -IPods/FirebaseAuth/FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll -IPods/FirebaseAuth/FirebaseAuth/Sources/Backend/RPC/MultiFactor/SignIn -IPods/FirebaseAuth/FirebaseAuth/Sources/Backend/RPC/MultiFactor/Unenroll -IPods/FirebaseAuth/FirebaseAuth/Sources/Storage -IPods/FirebaseAuth/FirebaseAuth/Sources/Utilities -IPods/FirebaseAuth/FirebaseAuth/Sources/Public/FirebaseAuth -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider/GameCenter -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider/Twitter -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider/OAuth -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider/Google -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider/GitHub -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider/Facebook -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider/Phone -IPods/FirebaseAuth/FirebaseAuth/Sources/AuthProvider/Email -IPods/FirebaseAppCheckInterop/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop -IPods/GTMSessionFetcher/Sources/Core -IPods/GTMSessionFetcher/Sources/Core/Public/GTMSessionFetcher -IPods/GoogleUtilities/third_party/IsAppEncrypted/Public -IPods/GoogleUtilities/GoogleUtilities/Logger/Public/GoogleUtilities -IPods/GoogleUtilities/GoogleUtilities/Network -IPods/GoogleUtilities/GoogleUtilities/Network/Public/GoogleUtilities -IPods/GoogleUtilities/GoogleUtilities/Common -IPods/GoogleUtilities/GoogleUtilities/Reachability -IPods/GoogleUtilities/GoogleUtilities/Reachability/Public/GoogleUtilities -IPods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Internal -IPods/GoogleUtilities/GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities -IPods/GoogleUtilities/GoogleUtilities/Environment/Public/GoogleUtilities -IPods/GoogleUtilities/GoogleUtilities/NSData+zlib/Public/GoogleUtilities Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m
Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.m:17:9:{17:9-17:59}: fatal error: 'FirebaseCore/Sources/FIRAnalyticsConfiguration.h' file not found [1]
 #import "FirebaseCore/Sources/FIRAnalyticsConfiguration.h"
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
scons: *** [Pods/FirebaseCore/FirebaseCore/Sources/FIRAnalyticsConfiguration.o] Error 1
scons: building terminated because of errors.

Solution

  • Looks like you want to compile FirebaseCore/FirebaseAuth from the sources pulled by Cocoapods. Cocoapods is meant to be integrated by Xcode, and if you want to compile your static library from the source codes of Firebase, It's possible, but I guess we should learn the podspec of Firebase. It is just too complicated.

    It is better to link against the compiled xcframeworks from Firebase:

    enter image description here

    Then I downloaded the compiled xcframeworks, removed the cocoapods, the xcworkspace project and other unnecessary files. Made some changes to your Sconstruct file:

    if env['simulator']:
        env.Append(LIBPATH=['Firebase/FirebaseAuth.xcframework/ios-arm64_x86_64-simulator'])
        env.Append(CPPPATH=['Firebase/FirebaseAuth.xcframework/ios-arm64_x86_64-simulator/FirebaseAuth.framework/Headers'])
        env.Append(LIBPATH=['Firebase/FirebaseCore.xcframework/ios-arm64_x86_64-simulator/FirebaseCore.framework'])
        env.Append(CPPPATH=['Firebase/FirebaseCore.xcframework/ios-arm64_x86_64-simulator/FirebaseCore.framework/Headers'])
    else:
        env.Append(LIBPATH=['Firebase/FirebaseAuth.xcframework/ios-arm64'])
        env.Append(CPPPATH=['Firebase/FirebaseAuth.xcframework/ios-arm64/FirebaseAuth.framework/Headers'])
        env.Append(LIBPATH=['Firebase/FirebaseCore.xcframework/ios-arm64/FirebaseCore.framework'])
        env.Append(CPPPATH=['Firebase/FirebaseCore.xcframework/ios-arm64/FirebaseCore.framework/Headers'])
    
    

    You will also need to change angled imports to quoted include:

    // #import <FirebaseCore/FirebaseCore.h>
    // #import <FirebaseAuth/FirebaseAuth.h>
    
    #include "FirebaseCore.h"
    #include "FirebaseAuth.h"
    

    After these modifications you can compile the project.

    scons: Reading SConscript files ...
    scons: done reading SConscript files.
    scons: Building targets ...
    g++ -o arithematic/arithematic.o -c -fomit-frame-pointer -O2 -ftree-vectorize -DNDEBUG -DNS_BLOCK_ASSERTIONS=1 -DDEBUG_ENABLED -DVULKAN_ENABLED -std=gnu++17 -DVERSION_4_0 -DIOS_ENABLED -DNEED_LONG_INT -DLIBYUV_DISABLE_NEON -DIOS_ENABLED -DUNIX_ENABLED -DCOREAUDIO_ENABLED -fmodules -fcxx-modules -miphoneos-version-min=10.0 -fobjc-arc -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -fno-exceptions -Wno-ambiguous-macro -Wall -Werror=return-type -arch arm64 -isysroot -stdlib=libc++ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.5.sdk -DPTRCALL_ENABLED -I. -Igodot -Igodot/platform/ios -IFirebase/FirebaseAuth.xcframework/ios-arm64/FirebaseAuth.framework/Headers -IFirebase/FirebaseCore.xcframework/ios-arm64/FirebaseCore.framework/Headers arithematic/arithematic.mm
    scons: `bin/libarithematic.arm64-iphone.release_debug.a' is up to date.
    scons: done building targets.
    

    BTW, above workaround is in this github Repo