[Tech Report] iMaxima: ECL on iOS, cross-compiling
About
ECL (*E*mbeddable *C*ommon *L*isp) provides cross-compiling functionality to iOS or Android platform (INSTALL#L106).
This post is a minimal tech report on how to possibliy port an existing CL application (Maxima, for example) to iOS environment.
The main process could be described as three steps:
- cross compiling ECL for iOS [easy as instructed in INSTALL#L106]
- cross compiling CL libraries
- load ECL and CL libraries in iOS and interacting between CL and C/ObjC/Swift
Cross Compiling ECL
As described in INSTALL#L106:
- Configuration and make
export IOS_VERSION_MIN="8.0" export IOS_SDK_DIR="`xcode-select --print-path`/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/" export CC="clang" export CXX="clang++" export CFLAGS="-arch arm64 -miphoneos-version-min=${IOS_VERSION_MIN} -isysroot ${IOS_SDK_DIR}" export CFLAGS="$CFLAGS -pipe -Wno-trigraphs -Wreturn-type -Wunused-variable" export CFLAGS="$CFLAGS -fpascal-strings -fasm-blocks -fmessage-length=0 -fvisibility=hidden" export CFLAGS="$CFLAGS -O2 -DNO_ASM" export LD="ld" export LDFLAGS="-arch arm64 -pipe -std=c99 -gdwarf-2 -isysroot ${IOS_SDK_DIR}" export LIBS="-framework Foundation" export CFLAGS="$CFLAGS -DGC_DISABLE_INCREMENTAL -DECL_RWLOCK" export CXXFLAGS="$CFLAGS" export ECL_TO_RUN=ecl ./configure --host=aarch64-apple-darwin \ --prefix=`pwd`/ecl-iOS \ --disable-c99complex \ --disable-shared \ --with-cross-config=`pwd`/src/util/iOS-arm64.cross_config make -j$`nproc` make install
- iOS Build Settings
- Other Linker Flags:
-leclgc -lecl -leclatomic -leclgmp # add when needed - Search Paths
- Header Search Paths
- Library Search Paths
Note: it is recommend to just copy ecl include and lib to the source of your iOS app:
export IOS_APP_SRC=/path/to/iMaxima/iMaxima/ cp -r $ECL_SRC_DIR/ecl-iOS/include $ECL_SRC_DIR/ecl-iOS/lib $IOS_APP_SRC
If XCode could configure programmatically…
- Other Linker Flags:
- iOS Build Phases
Booting ECL needs setting
SYSpath via environment variableECLDIR, which should be copied from$ECL_SRC_DIR/ecl-iOS/include/ecl.Thus thoese files should be copied in iOS Build Phases.
You might got the building script from cc-ecl.sh.
Cross Compiling CL libraries
According to ECL's manual Cross compilation, a CL library should be compiled like:
(require 'cmp)
(defvar *target*
(c:read-target-info
;; or just replace like
;; "/path/to/ecl-iOS/lib/ecl-26.5.5/target-info.lsp"
(merge-pathname "ecl-iOS/lib/ecl-26.5.5/target-info.lsp"
(uiop:getenv "ECL_SRC_DIR"))))
(dolist (src lisp-files-to-compile)
(compile-file src :target *target* :system-p t :output-file out))
(with-compilation-unit (:target *target*)
(c:build-static-library lib-name
:lisp-files outs
:init-name init-c-function-name-string))
Note: if you want some kind of automation, you might refer cross-maxima.lisp. The idea is using ASDF to compute dependency in order. (However, some library might using ASDF feature which might cause problems)
Load in iOS and interacting
ECL libraries
As described in ECL manual Embedding ECL, to load built libraries:
extern void ecl_init_maxima(cl_object);
void ecl_load_lib() {
ecl_init_module(NULL, ecl_init_maxima);
}
ECL provides some prebuilt libraries in lib/ecl-*.*.*, for example:
> ls lib*.a libasdf.a libecl-curl.a libql-minitar.a libcmp.a libecl-help.a libsb-bsd-sockets.a libdeflate.a libecl-quicklisp.a libsockets.a libecl-cdb.a libpackage-locks.a
It has to be noted that those libraries init name is as init_lib_ASDF
for libasdf.a.
Files in iOS sandbox
Within iOS, files are protected in sandbox model.
So to make ECL run happily, has to set ECLDIR and HOME and
other possibliy environment variables correctly:
void maxima_init(const char *ecldir, const char *home) {
setenv("ECLDIR", ecldir, 1);
setenv("HOME", home, 1);
char *argv[] = { "ecl", "-dir", (char *)home, NULL };
cl_boot(3, argv);
// ecl_init_module(NULL, ...);
// ...
}
// Copy eclSYSPath -> /Library/ecl
let fileManager = FileManager.default
guard let libURL = fileManager.urls(for: .libraryDirectory, in: .userDomainMask).first else { return }
guard let bundleMainPath = Bundle.main.resourcePath else { return }
let eclSYSDestURL = libURL.appendingPathComponent("ecl", isDirectory: true)
let eclSYSDestPath = eclSYSDestURL.path
let eclSYSSourcePath = (bundleMainPath as NSString).appendingPathComponent("ecl")
let eclSYSPath = (bundleMainPath as NSString).appendingPathComponent("ecl/help.doc")
do {
if fileManager.fileExists(atPath: eclSYSDestPath) {
try fileManager.removeItem(at: eclSYSDestURL)
}
try fileManager.copyItem(atPath: eclSYSSourcePath, toPath: eclSYSDestPath)
print("cp \(eclSYSSourcePath) \(eclSYSDestPath)")
} catch {
return
}
// HOME=/Directory/
guard let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let documentsPath = (documentsURL.path as NSString).appendingPathComponent(".maxima")
maxima_init(strdup(eclSYSPath), strdup(documentsPath))
ECL communicating with C
Should refer to examples/embed/hello.c.
Or, you might refer iMaxima's code: ecl_boost.c.
End
Hope more ECL on iOS application could be seen in the future. I want some performance optimize. :p