Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build cocoa touch framework for all architectures dependent on other frameworks added using cocoapods?

I want to create a private cocoapod of the output framework built for both simulators and devices.

I have created a Cocoa touch framework in which I have integrated CocoaLumberjack using Cocoapods. I want to build the framework for all architectures possible (simulator as well as device).

By default the build setting,

 'Build Active Architectures Only' is set to (Debug - Yes, Release - No).

As soon as I set this setting for debug to No, the build fails with the following linker error:

Undefined symbols for architecture i386:
"_OBJC_CLASS_$_DDASLLogger", referenced from:
  objc-class-ref in MyManager.o
"_OBJC_CLASS_$_DDLog", referenced from:
  objc-class-ref in MyManager.o
"_OBJC_CLASS_$_DDTTYLogger", referenced from:
  objc-class-ref in MyManager.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I get it that CocoaLumberjack is available for only active architectures in debug version.

So I switch back the Build active architectures for debug to Yes and build the framework successfully.

To build the framework for all the architectures I am using a run script added in Build phases that also claims to merge the ios-device and ios-simulator build into one. Here is the script:

    set -e
    set +u
    # Avoid recursively calling this script.
    if [[ $SF_MASTER_SCRIPT_RUNNING ]]
    then
    exit 0
    fi
    set -u
    export SF_MASTER_SCRIPT_RUNNING=1


    # Constants
    SF_TARGET_NAME=${PROJECT_NAME}
    UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal

    # Take build target
    if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]
    then
    SF_SDK_PLATFORM=${BASH_REMATCH[1]}
    else
    echo "Could not find platform name from SDK_NAME: $SDK_NAME"
    exit 1
    fi

    if [[ "$SF_SDK_PLATFORM" = "iphoneos" ]]
    then
    echo "Please choose iPhone simulator as the build target."
    exit 1
    fi

    IPHONE_DEVICE_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos

    # Build the other (non-simulator) platform
    xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/arm64" SYMROOT="${SYMROOT}" ARCHS='arm64' VALID_ARCHS='arm64' $ACTION

    xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}"  CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/armv7" SYMROOT="${SYMROOT}" ARCHS='armv7 armv7s' VALID_ARCHS='armv7 armv7s' $ACTION

    # Copy the framework structure to the universal folder (clean it first)
    rm -rf "${UNIVERSAL_OUTPUTFOLDER}"
    mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
    cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework"

    # Smash them together to combine all architectures
    lipo -create  "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/arm64/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/armv7/${PROJECT_NAME}.framework/${PROJECT_NAME}" -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}"

I check the 'Run script only when installing' checkbox present below the run script. Now I build the framework for Generic iOS device. Now i click on the Products group present in the Project hierarchy and select the MyFramework.framework file, right click and select show in finder. It opens up the ~/Library/Developer/Xcode/DerivedData/MyFramework-dlpsipmxkqmemwgqrfeovlzgyhca/Build/Products/Debug-iphoneos/MyFramework.framework in finder.

Now, I create a new tag 'MyFramework-v0.0.1' containing the commit where I added MyFramework.framework file. 

I go to ~/.cocoapods/repos/MyRepoName/ and create a podspec file MyFramework.podspec as follows:

Pod::Spec.new do |s|
s.name         = "MyFramework"
s.version      = "0.0.1"
s.summary      = "A pod for MyFramework"
s.description  = "A pod designed for MyFramework"

s.homepage     = "My_Private_Repo_Path"
s.license      = { :type => "MIT", :file => "FILE_LICENSE" }
s.authors             = { "My_Username" => "my_email_address"
}
s.platform     = :ios, "8.0"
s.ios.deployment_target = "8.0"

s.source       = { :git => "My_Private_Repo_Path", :tag => 'MyFramework-v'+String(s.version) }

s.requires_arc = true
s.vendored_frameworks = "MyFramework.framework"
s.dependency "CocoaLumberjack"
end

Now when I run the following command in the terminal:

pod repo push MyRepoName MyFramework.podspec

I get the following Error:

Validating spec
-> MyFramework (0.0.1)
- ERROR | [iOS] xcodebuild: Returned an unsuccessful exit code. You can use `   --verbose` for more information.
- NOTE  | [iOS] xcodebuild:  ld: warning: ignoring file  MyFramework/MyFramework.framework/MyFramework, missing required architecture i386 in file   MyFramework/MyFramework.framework/MyFramework (2 slices)
- NOTE  | [iOS] xcodebuild:  ld: warning: ignoring file MyFramework/MyFramework.framework/MyFramework, missing required architecture x86_64 in file MyFramework/MyFramework.framework/MyFramework (2 slices)
- NOTE  | [iOS] xcodebuild:  fatal error: lipo: -remove's specified would result in an empty fat file

[!] The `MyFramework.podspec` specification does not validate.

How to build cocoa touch framework for all devices and simulators, that is dependent on another framework (CocoaLumberjack) added using cocoapods? I need to create a private pod of the output framework.

like image 321
Saurabh Bhatia Avatar asked Jul 30 '16 06:07

Saurabh Bhatia


People also ask

What is Cocoa Touch frameworks?

Cocoa and Cocoa Touch are the application development environments for OS X and iOS, respectively. Both Cocoa and Cocoa Touch include the Objective-C runtime and two core frameworks: Cocoa, which includes the Foundation and AppKit frameworks, is used for developing applications that run on OS X.


2 Answers

So basically I found out that I just need to follow these simple steps.

1. Create a cocoa touch framework.
2. Set bitcode enabled to No.
3. Select your target and choose edit schemes. Select Run and choose Release from Info tab. 
4. No other setting required.
5. Now build the framework for any simulator as simulator runs on x86 architecture.
6. Click on Products group in Project Navigator and find the .framework file. 
7. Right click on it and click on Show in finder. Copy and paste it in any folder, I personally prefer the name 'simulator'.
8. Now build the framework for Generic iOS Device and follow the steps 6 through 
9. Just rename the folder to 'device' instead of 'simulator'.
10. Copy the device .framework file and paste in any other directory. I prefer the immediate super directory of both.

So the directory structure now becomes:

 - Desktop
   - device
     - MyFramework.framework
   - simulator
     - MyFramework.framework
   - MyFramework.framework

Now open terminal and cd to the Desktop. Now start typing the following command:

lipo -create 'device/MyFramework.framework/MyFramework' 'simulator/MyFramework.framework/MyFramework' -output 'MyFramework.framework/MyFramework'

and that's it. Here we merge the simulator and device version of MyFramework binary present inside MyFramework.framework. We get a universal framework that builds for all the architectures including simulator and device.

Now, creating a pod for this framework doesn't make any difference. It works like a charm. Please also note that there are run scripts available too to achieve the same functionality, but I spent a lot of time in finding the correct script. So I would suggest you use this method.

like image 162
Saurabh Bhatia Avatar answered Oct 13 '22 00:10

Saurabh Bhatia


XCode 11

First note that you can not use a fat framework with simulator support (x84_64 arch) for publishing to AppStore so you need to make two fat frameworks: one for Release with ARM archs (devices only) and one for Debug - ARM and x86_64 archs.

You can put next scripts to your projects's folder to make fat frameworks from a command line:

  1. Makefile
BUILD_DIR = build
BUILD = @sh build.sh ${BUILD_DIR}

default:
    @echo "Build framework makefile"
    @echo "usage: make (release | debug | all | rebuild | clean)"

release:
    ${BUILD} Release YourTargetScheme # <- your target scheme

debug:
    ${BUILD} Debug YourTargetScheme

clean:
    rm -r ${BUILD_DIR}

all: release debug

rebuild: clean all

  1. build.sh

# Debug
set -x

# Params
BUILD_DIR=$1
CONFIGURATION=$2
SCHEME=$3

WORKSPACE=YourWorkspace.xcworkspace # <- your workspace file
DERIVED_DATA_PATH=$BUILD_DIR/DerivedData

# Destinations
IPNONEOS="generic/platform=iOS"
IPNONESIMULATOR="platform=iOS Simulator,name=iPhone 8"

# Build
if [ $CONFIGURATION = "Release" ]; then
    xcodebuild \
        -quiet \
        -showBuildTimingSummary \
        -workspace $WORKSPACE \
        -configuration $CONFIGURATION \
        -scheme $SCHEME \
        -derivedDataPath $DERIVED_DATA_PATH \
        -destination "$IPNONEOS"
else
    xcodebuild \
        -quiet \
        -showBuildTimingSummary \
        -workspace $WORKSPACE \
        -configuration $CONFIGURATION \
        -scheme $SCHEME \
        -derivedDataPath $DERIVED_DATA_PATH \
        -destination "$IPNONEOS" \
        -destination "$IPNONESIMULATOR"
fi

# Move
FRAMEWORK=$SCHEME.framework
FRAMEWORK_PATH=$BUILD_DIR/$CONFIGURATION/$FRAMEWORK

mkdir $BUILD_DIR/$CONFIGURATION
rm -r $FRAMEWORK_PATH

if [ $CONFIGURATION = "Release" ]; then
    mv $DERIVED_DATA_PATH/Build/Products/Release-iphoneos/$FRAMEWORK $FRAMEWORK_PATH
else
    mv $DERIVED_DATA_PATH/Build/Products/Debug-iphoneos/$FRAMEWORK $FRAMEWORK_PATH

    BINARY_FILE=$FRAMEWORK_PATH/$SCHEME
    ARMV7=$FRAMEWORK_PATH/armv7
    ARM64=$FRAMEWORK_PATH/arm64
    x86_64=$FRAMEWORK_PATH/x86_64

    lipo $BINARY_FILE -extract armv7 -o $ARMV7
    lipo $BINARY_FILE -extract arm64 -o $ARM64
    cp $DERIVED_DATA_PATH/Build/Products/Debug-iphonesimulator/$FRAMEWORK/$SCHEME $x86_64

    lipo -create $ARMV7 $ARM64 $x86_64 -o $BINARY_FILE

    # Clean
    rm -rf $ARMV7 $ARM64 $x86_64
fi

Run one of these commands inside your project folder to build needed frameworks:

make all # Debug and Release frameworks
make release # Release only for devices and AppStore (armv7 and arm64 archs)
make debug # Debug with simulator support (armv7, arm64 and x86_64 archs)

Then you can find your fat frameworks in build directory inside your project's folder.

like image 21
iUrii Avatar answered Oct 13 '22 00:10

iUrii