Compiling Lua for iOS

To compile Lua for use on iPhone and iPad, I’ve grabbed the original source code for 5.1.4, and I have grabbed build_for_iphoneos.sh shell script by Christopher Stawarz. Both were slightly modified since Lua does not use autoconf, and build_for_iphoneos.sh is slightly dated. Read more to see what did I do.

You can read the full description with full copies of the files that need to be changed, or you can review diffs on the bottom of the post.

Full description

In “build_for_iphoneos.sh” I had to remove use of configure. I added a call to “make”. I’ve exported “prefix” so I can use it in modified Lua’s Makefile. Additionally, the script presumes older iOS SDKs will be shipped in newer Xcode releases, which isn’t the case for quite a while now; instead one needs to build with the latest SDK and specify that an older iPhone is a target.

Here’s the modified script:

#!/bin/bash

################################################################################
#
# Copyright (c) 2008-2009 Christopher J. Stawarz
# Modifications (c) 2011 Ivan Vucica
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
################################################################################



# Disallow undefined variables
set -u


default_gcc_version=4.2
default_iphoneos_version=3.1.2
default_macos_version=10.5

current_iphone_sdk=4.2

GCC_VERSION="${GCC_VERSION:-$default_gcc_version}"
export IPHONEOS_SDK="${IPHONEOS_DEPLOYMENT_TARGET:-$current_iphone_sdk}"
export IPHONEOS_DEPLOYMENT_TARGET="${IPHONEOS_DEPLOYMENT_TARGET:-$default_iphoneos_version}"
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-$default_macos_version}"


usage ()
{
    cat >&2 << EOF
Usage: ${0##*/} [-ht] [-p prefix] target [configure_args]
	-h	Print help message
	-p	Installation prefix (default: \$HOME/Developer/Platforms/...)
	-t	Use 16-bit Thumb instruction set (instead of 32-bit ARM)

The target must be "device" or "simulator".  Any additional arguments
are passed to configure.

The following environment variables affect the build process:

	GCC_VERSION			(default: $default_gcc_version)
	IPHONEOS_DEPLOYMENT_TARGET	(default: $default_iphoneos_version)
	MACOSX_DEPLOYMENT_TARGET	(default: $default_macos_version)

EOF
}


while getopts ":hp:t" opt; do
    case $opt in
	h  ) usage ; exit 0 ;;
	p  ) prefix="$OPTARG" ;;
	t  ) thumb_opt=thumb ;;
	\? ) usage ; exit 2 ;;
    esac
done
shift $(( $OPTIND - 1 ))

if (( $# < 1 )); then
    usage
    exit 2
fi

target=$1
shift

case $target in

    device )
	arch=armv6
	platform=iPhoneOS
	extra_cflags="-m${thumb_opt:-no-thumb} -mthumb-interwork"
	;;

    simulator )
	arch=i386
	platform=iPhoneSimulator
	extra_cflags="-D__IPHONE_OS_VERSION_MIN_REQUIRED=${IPHONEOS_DEPLOYMENT_TARGET%%.*}0000"
	;;

    * )
	usage
	exit 2

esac


platform_dir="/Developer/Platforms/${platform}.platform/Developer"
platform_bin_dir="${platform_dir}/usr/bin"
platform_sdk_dir="${platform_dir}/SDKs/${platform}${IPHONEOS_SDK}.sdk"
prefix="${prefix:-${HOME}${platform_sdk_dir}}"

export CC="${platform_bin_dir}/gcc-${GCC_VERSION}"
export CFLAGS="-arch ${arch} -pipe -Os -gdwarf-2 -isysroot ${platform_sdk_dir} ${extra_cflags}"
export LDFLAGS="-arch ${arch} -isysroot ${platform_sdk_dir}"
export CXX="${platform_bin_dir}/g++-${GCC_VERSION}"
export CXXFLAGS="${CFLAGS}"
export CPP="/Developer/usr/bin/cpp-${GCC_VERSION}"
export CXXCPP="${CPP}"


#./configure \
#    --prefix="${prefix}" \
#    --host="${arch}-apple-darwin" \
#    --disable-shared \
#    --enable-static \
#    "$@" || exit
export prefix
make "$@" || exit
make install "$@" || exit

cat >&2 << EOF

Build succeeded!  Files were installed in

  $prefix

EOF

I’ve called this modified script “build_for_iphoneos_noconfigure.sh”, and just like the original one, I’ve placed it in my path for easier access at a later time.

Next, we need to make slight adaptations to the Makefile in “src/” subdirectory of lua-5.1.4. Primarily, we need to introduce use of LDFLAGS in addition to MYLDFLAGS in a few spots, since they’re set by “build_for_iphoneos_noconfigure.sh”. We need to remove setting of CC and CFLAGS, since they will also be set by the shell script.

# makefile for building Lua
# see ../INSTALL for installation instructions
# see ../Makefile and luaconf.h for further customization

# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================

# Your platform. See PLATS for possible values.
PLAT= none

#CC= gcc
#CFLAGS= -O2 -Wall $(MYCFLAGS)
AR= ar rcu
RANLIB= ranlib
RM= rm -f
LIBS= -lm $(MYLIBS)

MYCFLAGS=
MYLDFLAGS=
MYLIBS=

# == END OF USER SETTINGS. NO NEED TO CHANGE ANYTHING BELOW THIS LINE =========

PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris

LUA_A=	liblua.a
CORE_O=	lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o \
	lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o  \
	lundump.o lvm.o lzio.o
LIB_O=	lauxlib.o lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o \
	lstrlib.o loadlib.o linit.o

LUA_T=	lua
LUA_O=	lua.o

LUAC_T=	luac
LUAC_O=	luac.o print.o

ALL_O= $(CORE_O) $(LIB_O) $(LUA_O) $(LUAC_O)
ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T)
ALL_A= $(LUA_A)

default: $(PLAT)

all:	$(ALL_T)

o:	$(ALL_O)

a:	$(ALL_A)

$(LUA_A): $(CORE_O) $(LIB_O)
	$(AR) $@ $?
	$(RANLIB) $@

$(LUA_T): $(LUA_O) $(LUA_A)
	$(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS) $(LDFLAGS)

$(LUAC_T): $(LUAC_O) $(LUA_A)
	$(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS) $(LDFLAGS)

clean:
	$(RM) $(ALL_T) $(ALL_O)

depend:
	@$(CC) $(CFLAGS) -MM l*.c print.c

echo:
	@echo "PLAT = $(PLAT)"
	@echo "CC = $(CC)"
	@echo "CFLAGS = $(CFLAGS)"
	@echo "AR = $(AR)"
	@echo "RANLIB = $(RANLIB)"
	@echo "RM = $(RM)"
	@echo "MYCFLAGS = $(MYCFLAGS)"
	@echo "MYLDFLAGS = $(MYLDFLAGS)"
	@echo "MYLIBS = $(MYLIBS)"

# convenience targets for popular platforms

none:
	@echo "Please choose a platform:"
	@echo "   $(PLATS)"

aix:
	$(MAKE) all CC="xlc" CFLAGS="-O2 -DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-ldl" MYLDFLAGS="-brtl -bexpall"

ansi:
	$(MAKE) all MYCFLAGS=-DLUA_ANSI

bsd:
	$(MAKE) all MYCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-Wl,-E"

freebsd:
	$(MAKE) all MYCFLAGS="-DLUA_USE_LINUX" MYLIBS="-Wl,-E -lreadline"

generic:
	$(MAKE) all MYCFLAGS=

linux:
	$(MAKE) all MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-Wl,-E -ldl -lreadline -lhistory -lncurses"

macosx:
	$(MAKE) all MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-lreadline"
# use this on Mac OS X 10.3-
#	$(MAKE) all MYCFLAGS=-DLUA_USE_MACOSX

mingw:
	$(MAKE) "LUA_A=lua51.dll" "LUA_T=lua.exe" \
	"AR=$(CC) -shared -o" "RANLIB=strip --strip-unneeded" \
	"MYCFLAGS=-DLUA_BUILD_AS_DLL" "MYLIBS=" "MYLDFLAGS=-s" lua.exe
	$(MAKE) "LUAC_T=luac.exe" luac.exe

posix:
	$(MAKE) all MYCFLAGS=-DLUA_USE_POSIX

solaris:
	$(MAKE) all MYCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-ldl"

# list targets that do not create files (but not all makes understand .PHONY)
.PHONY: all $(PLATS) default o a clean depend echo none

# DO NOT DELETE

lapi.o: lapi.c lua.h luaconf.h lapi.h lobject.h llimits.h ldebug.h \
  lstate.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h \
  lundump.h lvm.h
lauxlib.o: lauxlib.c lua.h luaconf.h lauxlib.h
lbaselib.o: lbaselib.c lua.h luaconf.h lauxlib.h lualib.h
lcode.o: lcode.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \
  lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h lgc.h \
  ltable.h
ldblib.o: ldblib.c lua.h luaconf.h lauxlib.h lualib.h
ldebug.o: ldebug.c lua.h luaconf.h lapi.h lobject.h llimits.h lcode.h \
  llex.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h \
  lfunc.h lstring.h lgc.h ltable.h lvm.h
ldo.o: ldo.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \
  lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lparser.h lstring.h \
  ltable.h lundump.h lvm.h
ldump.o: ldump.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h \
  lzio.h lmem.h lundump.h
lfunc.o: lfunc.c lua.h luaconf.h lfunc.h lobject.h llimits.h lgc.h lmem.h \
  lstate.h ltm.h lzio.h
lgc.o: lgc.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \
  lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h
linit.o: linit.c lua.h luaconf.h lualib.h lauxlib.h
liolib.o: liolib.c lua.h luaconf.h lauxlib.h lualib.h
llex.o: llex.c lua.h luaconf.h ldo.h lobject.h llimits.h lstate.h ltm.h \
  lzio.h lmem.h llex.h lparser.h lstring.h lgc.h ltable.h
lmathlib.o: lmathlib.c lua.h luaconf.h lauxlib.h lualib.h
lmem.o: lmem.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \
  ltm.h lzio.h lmem.h ldo.h
loadlib.o: loadlib.c lua.h luaconf.h lauxlib.h lualib.h
lobject.o: lobject.c lua.h luaconf.h ldo.h lobject.h llimits.h lstate.h \
  ltm.h lzio.h lmem.h lstring.h lgc.h lvm.h
lopcodes.o: lopcodes.c lopcodes.h llimits.h lua.h luaconf.h
loslib.o: loslib.c lua.h luaconf.h lauxlib.h lualib.h
lparser.o: lparser.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \
  lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h \
  lfunc.h lstring.h lgc.h ltable.h
lstate.o: lstate.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \
  ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h llex.h lstring.h ltable.h
lstring.o: lstring.c lua.h luaconf.h lmem.h llimits.h lobject.h lstate.h \
  ltm.h lzio.h lstring.h lgc.h
lstrlib.o: lstrlib.c lua.h luaconf.h lauxlib.h lualib.h
ltable.o: ltable.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \
  ltm.h lzio.h lmem.h ldo.h lgc.h ltable.h
ltablib.o: ltablib.c lua.h luaconf.h lauxlib.h lualib.h
ltm.o: ltm.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h lzio.h \
  lmem.h lstring.h lgc.h ltable.h
lua.o: lua.c lua.h luaconf.h lauxlib.h lualib.h
luac.o: luac.c lua.h luaconf.h lauxlib.h ldo.h lobject.h llimits.h \
  lstate.h ltm.h lzio.h lmem.h lfunc.h lopcodes.h lstring.h lgc.h \
  lundump.h
lundump.o: lundump.c lua.h luaconf.h ldebug.h lstate.h lobject.h \
  llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lstring.h lgc.h lundump.h
lvm.o: lvm.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \
  lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h ltable.h lvm.h
lzio.o: lzio.c lua.h luaconf.h llimits.h lmem.h lstate.h lobject.h ltm.h \
  lzio.h
print.o: print.c ldebug.h lstate.h lua.h luaconf.h lobject.h llimits.h \
  ltm.h lzio.h lmem.h lopcodes.h lundump.h

# (end of Makefile)

Finally, you need to introduce a slight change to the Makefile in root of Lua’s distribution. INSTALL_TOP needs to be a replica of “prefix” environment variable since it’s set by the shell script.

Here’s just the excerpt of the top of the Makefile. I think you’ll easily notice what’s

# makefile for installing Lua
# see INSTALL for installation instructions
# see src/Makefile and src/luaconf.h for further customization

# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================

# Your platform. See PLATS for possible values.
PLAT= none

# Where to install. The installation starts in the src and doc directories,
# so take care if INSTALL_TOP is not an absolute path.
INSTALL_TOP= ${prefix}
INSTALL_BIN= $(INSTALL_TOP)/bin
INSTALL_INC= $(INSTALL_TOP)/include
INSTALL_LIB= $(INSTALL_TOP)/lib
INSTALL_MAN= $(INSTALL_TOP)/man/man1

###### -- continues below --
 

That’s it. Now you can compile Lua for platform “generic” (trying to compile for “macosx” pulls in readline which isn’t available on iPhone):

build_for_iphoneos_noconfigure.sh device generic

Output should end with:

Build succeeded!  Files were installed in

  /Users/ivucica/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.2.sdk

Excerpt from documentation for “build_for_iphoneos.sh”, which explains how to set up your Xcode project:

By default, the script will install files in a subdirectory of $HOME/Developer/Platforms that mirrors the layout of /Developer/Platforms. For example, the installation prefix for a device build defaults to $HOME/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.1.sdk. The advantage of this is that you can make Xcode aware of the installed files for both the simulator and the device by adding the following under “All Configurations” in your project settings:

Header Search Paths: $(HOME)/$(SDK_DIR)/include
Library Search Paths: $(HOME)/$(SDK_DIR)/lib

Of course, you can move these things into a better place, or you can attempt to design an SDK, similar to what I’ve done when adding iPhone support to libapril and friends.

Just the diffs

Using Mercurial I quickly made the following diff for lua-5.1.4, if you’re interested only in diff on Makefiles:

diff -r 130652fce1b4 Makefile
--- a/Makefile  Fri Mar 04 14:13:36 2011 +0100
+++ b/Makefile  Fri Mar 04 14:13:56 2011 +0100
@@ -9,7 +9,7 @@
 
 # Where to install. The installation starts in the src and doc directories,
 # so take care if INSTALL_TOP is not an absolute path.
-INSTALL_TOP= /usr/local
+INSTALL_TOP= ${prefix}
 INSTALL_BIN= $(INSTALL_TOP)/bin
 INSTALL_INC= $(INSTALL_TOP)/include
 INSTALL_LIB= $(INSTALL_TOP)/lib
diff -r 130652fce1b4 src/Makefile
--- a/src/Makefile      Fri Mar 04 14:13:36 2011 +0100
+++ b/src/Makefile      Fri Mar 04 14:13:56 2011 +0100
@@ -7,8 +7,8 @@
 # Your platform. See PLATS for possible values.
 PLAT= none
 
-CC= gcc
-CFLAGS= -O2 -Wall $(MYCFLAGS)
+#CC= gcc
+#CFLAGS= -O2 -Wall $(MYCFLAGS)
 AR= ar rcu
 RANLIB= ranlib
 RM= rm -f
@@ -52,10 +52,10 @@
        $(RANLIB) $@
 
 $(LUA_T): $(LUA_O) $(LUA_A)
-       $(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS)
+       $(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS) $(LDFLAGS)
 
 $(LUAC_T): $(LUAC_O) $(LUA_A)
-       $(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS)
+       $(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS) $(LDFLAGS)
 
 clean:
        $(RM) $(ALL_T) $(ALL_O)

And diff for the build_for_iphoneos.sh:

--- build_for_iphoneos.sh       2011-03-04 12:38:06.000000000 +0100
+++ build_for_iphoneos_noconfigure.sh   2011-03-04 13:48:05.000000000 +0100
@@ -36,7 +36,10 @@
 default_iphoneos_version=3.1.2
 default_macos_version=10.5
 
+current_iphone_sdk=4.2
+
 GCC_VERSION="${GCC_VERSION:-$default_gcc_version}"
+export IPHONEOS_SDK="${IPHONEOS_DEPLOYMENT_TARGET:-$current_iphone_sdk}"
 export IPHONEOS_DEPLOYMENT_TARGET="${IPHONEOS_DEPLOYMENT_TARGET:-$default_iphoneos_version}"
 export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-$default_macos_version}"
 
@@ -103,7 +106,7 @@
 
 platform_dir="/Developer/Platforms/${platform}.platform/Developer"
 platform_bin_dir="${platform_dir}/usr/bin"
-platform_sdk_dir="${platform_dir}/SDKs/${platform}${IPHONEOS_DEPLOYMENT_TARGET}.sdk"
+platform_sdk_dir="${platform_dir}/SDKs/${platform}${IPHONEOS_SDK}.sdk"
 prefix="${prefix:-${HOME}${platform_sdk_dir}}"
 
 export CC="${platform_bin_dir}/gcc-${GCC_VERSION}"
@@ -115,14 +118,15 @@
 export CXXCPP="${CPP}"
 
 
-./configure \
-    --prefix="${prefix}" \
-    --host="${arch}-apple-darwin" \
-    --disable-shared \
-    --enable-static \
-    "$@" || exit
-
-make install || exit
+#./configure \
+#    --prefix="${prefix}" \
+#    --host="${arch}-apple-darwin" \
+#    --disable-shared \
+#    --enable-static \
+#    "$@" || exit
+export prefix
+make "$@" || exit
+make install "$@" || exit
 
 cat >&2 << EOF
 


via blog.vucica.net

3 thoughts on “Compiling Lua for iOS

  1. rockstar

    It was great info. Thanks for sharing it. I was searching building lua myself iPhone, rather taking someone's else binary
    Thanks a lot

    Reply
  2. Geoff

    Nice write up, can i ask a couple of noob questions.
    1: what was the smallest file size you managed to acheive for the ios IPA.
    2: What method is there to access the UI from within the LUA?

    Regards

    Reply
    1. Ivan Vučica Post author

      1. I'm not sure what this has to do with Lua on iOS 🙂 but I never needed to 'go for the minimum'; I'm happy to have been developing apps while iPhone limited downloads over cell network to 30MB, so that was the goal I once needed to reach, but below that? No idea.

      2. I have never needed to do that. In fact, I had little need to actually use Lua; if I remember correctly, I was porting a game to iOS back in 2011, so I just needed to get Lua to work; I had little need to actually figure out how it's used.

      Therefore, my first (and wrong) thought was you could perhaps try telling Lua that various Objective-C runtime methods, such as `objc_msgSend()`, exist, and then try using those to call Objective-C directly.

      And that's wrong — and not because it may be hard to get a pointer to an Objective-C object (you could just use class methods instead). That approach will not fail just because satisfying the calling convention, esp variadic arguments one, may be tricky — it's because it looks like satisfying it is not really possible. See http://www.lua.org/pil/26.html — the functions must always return an `int` and must always accept `lua_State *` as the only argument.

      My actual advice is therefore to expose as much functionality as you actually need from Lua, and do so as C functions that call UI methods using Objective-C. Then, expose these with Lua's `lua_pushcfunction()` and `lua_setglobal()`. See http://www.lua.org/pil/26.1.html

      Reply

Leave a Reply

Your email address will not be published.

 

What is 12 + 12 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)

This site uses Akismet to reduce spam. Learn how your comment data is processed.