Monday, October 21, 2013

Xcode from the Command Line

Install the xcode command line utilities.

Xcode 4.5.2 > Preferences > Downloads > Command Line Tools > Install

Discover the location of compile/link binaries.

xcrun -sdk iphoneos -find gcc
xcrun -sdk iphonesimulator -find gcc
xcrun -sdk macosx -find gcc

Modify your linux make file for apple specifics.

# /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/llvm-gcc-4.2 -v --help
# 
# -O<number>                  Set optimization level to <number>
# -Os                         Optimize for space rather than speed calls
# -gdwarf-2                   Generate debug information in DWARF v2 format
# -isysroot <dir>             Set <dir> to be the system root directory
# -isystem <dir>              Add <dir> to the start of the system include path
# -mmacosx-version-min=       The earliest MacOS X version on which this program will run
# -miphoneos-version-min=     The earliest iPhone OS version on which this program will run

APPLE_MIN_MAC = 10.6
APPLE_MIN_PHONE = 40300
APPLE_COMPILE_VERSION = 4.2

ifeq ($(APPLE_MODE),iPhoneSimulator)

 APPLE_OS = i386
 APPLE_SDK_VERSION = 6.0
 APPLE_SDK = /Applications/Xcode.app/Contents/Developer/Platforms/$(APPLE_MODE).platform/Developer/SDKs/$(APPLE_MODE)$(APPLE_SDK_VERSION).sdk
 APPLE_BIN = /Applications/Xcode.app/Contents/Developer/Platforms/$(APPLE_MODE).platform/Developer/usr/bin
 APPLE_OPTIMIZE = -O1 -Os 

else
 ifeq ($(APPLE_MODE),iPhoneOS)

  APPLE_OS = armv7
  APPLE_SDK_VERSION = 6.0
  APPLE_SDK = /Applications/Xcode.app/Contents/Developer/Platforms/$(APPLE_MODE).platform/Developer/SDKs/$(APPLE_MODE)$(APPLE_SDK_VERSION).sdk
  APPLE_BIN = /Applications/Xcode.app/Contents/Developer/Platforms/$(APPLE_MODE).platform/Developer/usr/bin
  APPLE_OPTIMIZE = -O1 -Os 

 else # MacOSX

  APPLE_MODE = MacOSX
  APPLE_OS = x86_64
  APPLE_SDK_VERSION = 10.7
  APPLE_SDK = /Applications/Xcode.app/Contents/Developer/Platforms/$(APPLE_MODE).platform/Developer/SDKs/$(APPLE_MODE)$(APPLE_SDK_VERSION).sdk
  APPLE_BIN = /Applications/Xcode.app/Contents/Developer/usr/bin
  APPLE_OPTIMIZE = -O1

 endif
endif

CXX    = $(APPLE_BIN)/llvm-g++-$(APPLE_COMPILE_VERSION)
CC     = $(APPLE_BIN)/llvm-gcc-$(APPLE_COMPILE_VERSION)
CPP    = $(APPLE_BIN)/llvm-cpp-$(APPLE_COMPILE_VERSION)

ifeq ($(APPLE_DEBUG),TRUE)
 DEBUG_OPTS    = -O0 -gdwarf-2
 DEBUG_DEFINES = -D_DEBUG
else
 DEBUG_OPTS    = $(APPLE_OPTIMIZE)
 DEBUG_DEFINES = -DNDEBUG
endif

CXX_OPTS = $(DEBUG_OPTS) -arch $(APPLE_OS) -isysroot $(APPLE_SDK) -isystem $(APPLE_SDK)/usr/include -mmacosx-version-min=$(APPLE_MIN_MAC)

CXX_DEFINES = $(DEBUG_DEFINES) -D__IPHONE_OS_VERSION_MIN_REQUIRED=$(APPLE_MIN_PHONE)

Build on a macmini for execution on macmini, simulator and device. I'm assuming that your make file uses APPLE_MODE in the name of the folder for the output .o and .a files.

export APPLE_MODE=iPhoneSimulator
export APPLE_DEBUG=FALSE
cd /yourcodebase/
make -f yourmakefile

export APPLE_MODE=iPhoneOS
export APPLE_DEBUG=FALSE
cd /yourcodebase/
make -f yourmakefile

export APPLE_MODE=MacOSX
export APPLE_DEBUG=FALSE
cd /yourcodebase/
make -f yourmakefile

Even if your intention is to produce a static c++ lib for use on an iphone, you can still execute the Mac and Simulator builds from the Mac command line. Prior to setting up an xcode project to use the simulator and ios libs, it's best to combine them into a single universal lib.

mkdir /yourcodebse/Universal
lipo -arch i386 /yourcodebase/iPhoneSimulator/yourlib.a -arch armv7 /yourcodebase/iPhoneOS/yourlib.a -create -output /yourcodebse/Universal/yourlib.a

Here's how to setup an xcode project that uses your static lib.

Xcode
 Create A New Xcode Project
  iOS > Application > Single View Application
  > Next
   Product Name: demo
   [unchecked] Use Storyboards
     [checked] Use Automatic Reference Counting
   [unchecked] Include Unit Tests
   > Next
   [unchecked] Create local git repository for this project
   > Create

Add our static lib to the project.

[right-click] demo (1 target, iOS SDK 6.0) > New Group > [rename to 'libs']
[right-click] libs> Add Files to "demo"... > Macintosh HD > /yourcodebse/Universal 
 > Don't copy items..., Do create groups... > Add

Confirm this is set, so we can link to the static lib.

demo> Build Settings > All | Combined
 > Search Paths 
  > Library Search Paths = "/yourcodebse/Universal"

Add this, so we can see the static lib headers.

demo> Build Settings > All | Combined
 > Search Paths 
  > [double-click] Header Search Paths
   +  /yourcodebse/whatever   -- non-recursive 

Set these, so our view of the static lib headers is correct.

demo > Build Settings > All | Combined
 > Apple LLVM compiler 4.1 - Language
  > Other C Flags // C++ will inherit from here
   > Debug   > + > Any Architecture | Any SDK > -DanythingSpecificToYourHeaders
   > Release > + > Any Architecture | Any SDK > -DanythingSpecificToYourHeaders

Remove the DEBUG=1 preprocessor macro to prevent conflicts with source such as an enum that contains the term DEBUG.

demo > Build Settings > All | Combined
 > Apple LLVM compiler 4.1 - Preprocessing
  > Preprocessor Macros > [double-click]
   DEBUG=1 > [- button]
   $(inherited) > [- button] // otherwise DEBUG=1 still appears

In order to avoid the "undefined i386 std::ios_base" error, I had to specifically add the 6.0.9 library. The unversioned one wasn't enough. See stackoverflow for details.

demo > Build Phases
 > Link Binary With Libraries 
  > libstdc++.6.0.9.dylib

Create Worker.h and Worker.cpp

[right-click] demo (folder, not project) > New File
 > iOS > C and C++ > C++ Class > Next > Save As: Worker > Create

Create Wrapper.h and Wrapper.m

[right-click] demo (folder, not project) > New File
 > iOS > Coca Touch > Objective-C class > Next 
  Class: Wrapper
  Subclass of: NSObject
  > Next > Create

Rename to .mm makes it possible to call into c++ files.

[left-click] Wrapper.m > [left-click again] > rename to Wrapper.mm

Worker.h

#ifndef __demo__Worker__
#define __demo__Worker__

int doWork();

#endif

Worker.cpp

#include <iostream>
#include "Worker.h"
#include "yourobject.h"
int doWork()
{
  printf("\n work started");
  YourObject obj;
  obj.doSomething();
  printf("\n work finished");
  return 0;
}

Wrapper.h

#import <Foundation/Foundation.h>

@interface Wrapper : NSObject { }
+ (void)callStuff;
@end

Wrapper.mm

#import "Wrapper.h"
#include "Worker.h"

@implementation Wrapper

+ (void)callStuff
{
  NSLog(@"wrap started");
  doWork();
  NSLog(@"wrap finished");
}

@end

AppDelegate.m

...
#import ...
#import "Wrapper.h"
...
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ...
    // Override point for customization after application launch.
    [Wrapper callStuff];
    ...
}
...
ios
{ "loggedin": false, "owner": false, "avatar": "", "render": "nothing", "trackingID": "UA-36983794-1", "description": "", "page": { "blogIds": [ 458 ] }, "domain": "holtstrom.com", "base": "\/michael", "url": "https:\/\/holtstrom.com\/michael\/", "frameworkFiles": "https:\/\/holtstrom.com\/michael\/_framework\/_files.4\/", "commonFiles": "https:\/\/holtstrom.com\/michael\/_common\/_files.3\/", "mediaFiles": "https:\/\/holtstrom.com\/michael\/media\/_files.3\/", "tmdbUrl": "http:\/\/www.themoviedb.org\/", "tmdbPoster": "http:\/\/image.tmdb.org\/t\/p\/w342" }