diff --git a/recipes/audiostream.py b/recipes/audiostream.py deleted file mode 100644 index 51fded8..0000000 --- a/recipes/audiostream.py +++ /dev/null @@ -1,7 +0,0 @@ -from toolchain import Recipe - -class AudiostreamRecipe(Recipe): - version = "master" - url = "https://github.com/kivy/audiostream/archive/{version}.zip" - -recipe = AudiostreamRecipe() diff --git a/recipes/hostpython/ModulesSetup b/recipes/hostpython/ModulesSetup new file mode 100644 index 0000000..77a934a --- /dev/null +++ b/recipes/hostpython/ModulesSetup @@ -0,0 +1,52 @@ +posix posixmodule.c # posix (UNIX) system calls +errno errnomodule.c # posix (UNIX) errno values +pwd pwdmodule.c # this is needed to find out the user's home dir + # if $HOME is not set +_sre _sre.c # Fredrik Lundh's new regular expressions +_codecs _codecsmodule.c # access to the builtin codecs and codec registry +zipimport zipimport.c +_symtable symtablemodule.c +array arraymodule.c # array objects +cmath cmathmodule.c # -lm # complex math library functions +math mathmodule.c # -lm # math library functions, e.g. sin() +_struct _struct.c # binary structure packing/unpacking +time timemodule.c # -lm # time operations and variables +operator operator.c # operator.add() and similar goodies +_weakref _weakref.c # basic weak reference support +_random _randommodule.c # Random number generator +_collections _collectionsmodule.c # Container types +itertools itertoolsmodule.c # Functions creating iterators for efficient looping +strop stropmodule.c # String manipulations +_functools _functoolsmodule.c # Tools for working with functions and callable objects +_elementtree -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI _elementtree.c # elementtree accelerator +datetime datetimemodule.c # date/time type +_bisect _bisectmodule.c # Bisection algorithms +fcntl fcntlmodule.c # fcntl(2) and ioctl(2) +select selectmodule.c # select(2); not on ancient System V +_socket socketmodule.c +_md5 md5module.c md5.c +_sha shamodule.c +_sha256 sha256module.c +_sha512 sha512module.c +binascii binascii.c +parser parsermodule.c +cStringIO cStringIO.c +cPickle cPickle.c +zlib zlibmodule.c -I$(prefix)/include -L$(exec_prefix)/lib -lz +xxsubtype xxsubtype.c +unicodedata unicodedata.c # static Unicode character database + +# Theses modules are used by Kivy inside other module +# json in Settings, _io by zipfile... +_json _json.c +_io _io/bufferedio.c _io/bytesio.c _io/fileio.c _io/iobase.c _io/_iomodule.c _io/stringio.c _io/textio.c +_heapq _heapqmodule.c + +# Special inclusion for sqlite3 +_sqlite3 -DSQLITE_OMIT_LOAD_EXTENSION _sqlite/cache.c _sqlite/microprotocols.c _sqlite/row.c _sqlite/connection.c _sqlite/module.c _sqlite/statement.c _sqlite/cursor.c _sqlite/prepare_protocol.c _sqlite/util.c + +# Include expat +pyexpat expat/xmlparse.c expat/xmlrole.c expat/xmltok.c pyexpat.c -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI + +# Future (used by numpy) +future_builtins future_builtins.c diff --git a/recipes/hostpython/__init__.py b/recipes/hostpython/__init__.py new file mode 100644 index 0000000..bd742a7 --- /dev/null +++ b/recipes/hostpython/__init__.py @@ -0,0 +1,47 @@ +from toolchain import Recipe, shprint +from os.path import join +import sh +import shutil + + +class HostpythonRecipe(Recipe): + version = "2.7.1" + url = "https://www.python.org/ftp/python/{version}/Python-{version}.tar.bz2" + depends = ["libffi", ] + archs = ["i386"] + + def download(self): + super(HostpythonRecipe, self).download() + self.ctx.hostpython = join( + self.ctx.build_dir, "i386", self.archive_root, + "hostpython") + self.ctx.hostpgen = join( + self.ctx.build_dir, "i386", self.archive_root, + "Parser", "hostpgen") + print("Global: hostpython located at {}".format(self.ctx.hostpython)) + print("Global: hostpgen located at {}".format(self.ctx.hostpgen)) + + def prebuild_arch(self, arch): + if self.has_marker("patched"): + return + self.apply_patch("ssize-t-max.patch") + self.apply_patch("dynload.patch") + self.apply_patch("static-_sqlite3.patch") + self.copy_file("ModulesSetup", "Modules/Setup.local") + self.set_marker("patched") + + def build_i386(self): + sdk_path = sh.xcrun("--sdk", "macosx", "--show-sdk-path").strip() + + build_env = self.ctx.env.copy() + build_env["CC"] = "clang -Qunused-arguments -fcolor-diagnostics" + build_env["LDFLAGS"] = "-lsqlite3" + build_env["CFLAGS"] = "--sysroot={}".format(sdk_path) + configure = sh.Command(join(self.build_dir, "configure")) + shprint(configure, _env=build_env) + shprint(sh.make, "-C", self.build_dir, "-j4", "python.exe", "Parser/pgen", + _env=build_env) + shutil.move("python.exe", "hostpython") + shutil.move("Parser/pgen", "Parser/hostpgen") + +recipe = HostpythonRecipe() diff --git a/recipes/hostpython/dynload.patch b/recipes/hostpython/dynload.patch new file mode 100644 index 0000000..7cb73f6 --- /dev/null +++ b/recipes/hostpython/dynload.patch @@ -0,0 +1,24 @@ +--- Python-2.7.1/Python/dynload_shlib.c.orig 2011-12-05 00:00:00.000000000 +0100 ++++ Python-2.7.1/Python/dynload_shlib.c 2011-12-05 00:02:51.000000000 +0100 +@@ -84,6 +84,15 @@ + PyOS_snprintf(funcname, sizeof(funcname), + LEAD_UNDERSCORE "init%.200s", shortname); + ++ /* On IOS, dlopen crash as soon as we try to open one of our library. ++ * Instead, we have done a redirection of linking to convert our .so into a ++ * .a. Then the main executable is linked with theses symbol. So, instead ++ * of trying to dlopen, directly do the dlsym. ++ * -- Mathieu ++ */ ++ return (dl_funcptr) dlsym(RTLD_MAIN_ONLY, funcname); ++ ++#if 0 + if (fp != NULL) { + int i; + struct stat statb; +@@ -140,4 +149,5 @@ + handles[nhandles++].handle = handle; + p = (dl_funcptr) dlsym(handle, funcname); + return p; ++#endif + } diff --git a/recipes/hostpython/ssize-t-max.patch b/recipes/hostpython/ssize-t-max.patch new file mode 100644 index 0000000..3d63b49 --- /dev/null +++ b/recipes/hostpython/ssize-t-max.patch @@ -0,0 +1,17 @@ +diff -Naur Python-2.7.1.orig/Include/pyport.h Python-2.7.1/Include/pyport.h +--- Python-2.7.1.orig/Include/pyport.h 2010-09-14 18:10:22.000000000 +0200 ++++ Python-2.7.1/Include/pyport.h 2011-05-13 12:24:53.000000000 +0200 +@@ -186,9 +186,11 @@ + #endif + + /* Largest positive value of type Py_ssize_t. */ +-#define PY_SSIZE_T_MAX ((Py_ssize_t)(((size_t)-1)>>1)) ++//#define PY_SSIZE_T_MAX ((Py_ssize_t)(((size_t)-1)>>1)) + /* Smallest negative value of type Py_ssize_t. */ +-#define PY_SSIZE_T_MIN (-PY_SSIZE_T_MAX-1) ++//#define PY_SSIZE_T_MIN (-PY_SSIZE_T_MAX-1) ++#define PY_SSIZE_T_MAX TMP_MAX ++#define PY_SSIZE_T_MIN -TMP_MAX + + #if SIZEOF_PID_T > SIZEOF_LONG + # error "Python doesn't support sizeof(pid_t) > sizeof(long)" diff --git a/recipes/hostpython/static-_sqlite3.patch b/recipes/hostpython/static-_sqlite3.patch new file mode 100644 index 0000000..783c5ef --- /dev/null +++ b/recipes/hostpython/static-_sqlite3.patch @@ -0,0 +1,25 @@ +--- Python-2.7.1/Modules/_sqlite/module.c.orig 2012-10-28 02:30:58.000000000 +0200 ++++ Python-2.7.1/Modules/_sqlite/module.c 2012-10-28 02:28:12.000000000 +0200 +@@ -28,6 +28,9 @@ + #include "prepare_protocol.h" + #include "microprotocols.h" + #include "row.h" ++#ifndef MODULE_NAME ++#define MODULE_NAME "_sqlite3" ++#endif + + #if SQLITE_VERSION_NUMBER >= 3003003 + #define HAVE_SHARED_CACHE +--- Python-2.7.1/Modules/_sqlite/sqlitecompat.h.orig 2012-10-28 02:30:53.000000000 +0200 ++++ Python-2.7.1/Modules/_sqlite/sqlitecompat.h 2012-10-28 02:28:14.000000000 +0200 +@@ -26,6 +26,10 @@ + #ifndef PYSQLITE_COMPAT_H + #define PYSQLITE_COMPAT_H + ++#ifndef MODULE_NAME ++#define MODULE_NAME "_sqlite3" ++#endif ++ + /* define Py_ssize_t for pre-2.5 versions of Python */ + + #if PY_VERSION_HEX < 0x02050000 diff --git a/recipes/libffi/__init__.py b/recipes/libffi/__init__.py index b738c09..3a70617 100644 --- a/recipes/libffi/__init__.py +++ b/recipes/libffi/__init__.py @@ -1,33 +1,49 @@ from toolchain import Recipe, shprint +from os.path import join, exists import sh +import shutil class LibffiRecipe(Recipe): - version = "3.0.13" + version = "3.2.1" url = "ftp://sourceware.org/pub/libffi/libffi-{version}.tar.gz" + archs = ("armv7",) def prebuild_arch(self, arch): if self.has_marker("patched"): return - self.apply_patch("ffi-3.0.13-sysv.S.patch") - if arch in ("armv7", "armv7s", "arm64"): - shprint(sh.sed, - "-i.bak", - "s/-miphoneos-version-min=4.0/-miphoneos-version-min=6.0/g", - "generate-ios-source-and-headers.py") + # necessary as it doesn't compile with XCode 6.0. If we use 5.1.1, the + # compiler for i386 is not working. + shprint(sh.sed, + "-i.bak", + "s/-miphoneos-version-min=5.1.1/-miphoneos-version-min=6.0/g", + "generate-darwin-source-and-headers.py") self.set_marker("patched") def build_arch(self, arch): - if arch == "i386": - target_name = "libffi OS X" - else: - target_name = "libffi iOS" - shprint(sh.xcodebuild, "-project", "libffi.xcodeproj", - "-target", target_name, - "-configuration", "Release", - "-sdk", "iphoneos{}".format(self.ctx.sdkver), - "OTHER_CFLAGS=-no-integrated-as") + "-target", "libffi-iOS", + "-configuration", "Release") + + def assemble_to(self, filename): + shutil.copy(join( + self.get_build_dir("armv7"), + "build/Release-iphoneos/libffi.a"), + filename) + for sdkarch, arch in ( + ("iphoneos-arm64", "arm64"), + ("iphoneos-armv7", "armv7"), + ("iphonesimulator-i386", "i386"), + ("iphonesimulator-x86_64", "x86_64")): + dest_dir = join(self.ctx.dist_dir, "include", arch, "ffi") + if exists(dest_dir): + continue + shutil.copytree(join( + self.get_build_dir("armv7"), + "build_{}/include".format(sdkarch)), + join(self.ctx.dist_dir, "include", arch, "ffi")) + recipe = LibffiRecipe() + diff --git a/recipes/python/__init__.py b/recipes/python/__init__.py index 83622a6..a9ac8f4 100644 --- a/recipes/python/__init__.py +++ b/recipes/python/__init__.py @@ -1,24 +1,12 @@ from toolchain import Recipe, shprint from os.path import join import sh -import shutil class PythonRecipe(Recipe): version = "2.7.1" url = "https://www.python.org/ftp/python/{version}/Python-{version}.tar.bz2" - depends = ["libffi", ] - - def download(self): - super(PythonRecipe, self).download() - self.ctx.hostpython = join( - self.ctx.build_dir, "i386", self.archive_root, - "hostpython") - self.ctx.hostpgen = join( - self.ctx.build_dir, "i386", self.archive_root, - "Parser", "hostpgen") - print("Global: hostpython located at {}".format(self.ctx.hostpython)) - print("Global: hostpgen located at {}".format(self.ctx.hostpgen)) + depends = ["hostpython", "libffi", ] def prebuild_arch(self, arch): # common to all archs @@ -29,56 +17,30 @@ class PythonRecipe(Recipe): self.apply_patch("static-_sqlite3.patch") self.copy_file("ModulesSetup", "Modules/Setup.local") self.copy_file("_scproxy.py", "Lib/_scproxy.py") - #self.copy_file("Setup.dist", "Modules/Setup.dist") - - if arch in ("armv7", "armv7s", "arm64"): - self.apply_patch("xcompile.patch") - self.apply_patch("setuppath.patch") - self.append_file("ModulesSetup.mobile", "Modules/Setup.local") + self.apply_patch("xcompile.patch") + self.apply_patch("setuppath.patch") + self.append_file("ModulesSetup.mobile", "Modules/Setup.local") self.set_marker("patched") - def build_i386(self): - sdk_path = sh.xcrun( - "--sdk", "macosx", - "--show-sdk-path").splitlines()[0] - - build_env = self.ctx.env.copy() - build_env["CC"] = "clang -Qunused-arguments -fcolor-diagnostics" - build_env["LDFLAGS"] = "-lsqlite3" - build_env["CFLAGS"] = "--sysroot={}".format(sdk_path) - configure = sh.Command(join(self.build_dir, "configure")) - shprint(configure, _env=build_env) - shprint(sh.make, "-C", self.build_dir, "-j4", "python.exe", "Parser/pgen", - _env=build_env) - shutil.move("python.exe", "hostpython") - shutil.move("Parser/pgen", "Parser/hostpgen") - def build_arch(self, arch): - if self.has_marker("build"): - return - if arch == "i386": - super(PythonRecipe, self).build_arch(arch) - self.set_marker("build") - return - build_env = self.ctx.env.copy() - build_env["CC"] = sh.xcrun("-find", "-sdk", "iphoneos", "clang").splitlines()[0] - build_env["AR"] = sh.xcrun("-find", "-sdk", "iphoneos", "ar").splitlines()[0] - build_env["LD"] = sh.xcrun("-find", "-sdk", "iphoneos", "ld").splitlines()[0] + build_env["CC"] = sh.xcrun("-find", "-sdk", arch.sdk, "clang").strip() + build_env["AR"] = sh.xcrun("-find", "-sdk", arch.sdk, "ar").strip() + build_env["LD"] = sh.xcrun("-find", "-sdk", arch.sdk, "ld").strip() build_env["CFLAGS"] = " ".join([ - "-arch", arch, + "-arch", arch.arch, "-pipe", "-no-cpp-precomp", - "-isysroot", self.ctx.iossdkroot, + "-isysroot", arch.sysroot, "-O3", - "-miphoneos-version-min={}".format(self.ctx.sdkver)]) + "-miphoneos-version-min={}".format(arch.version_min)]) build_env["LDFLAGS"] = " ".join([ - "-arch", arch, + "-arch", arch.arch, "-undefined dynamic_lookup", "-Lextralibs/", "-lsqlite3", - "-isysroot", self.ctx.iossdkroot]) + "-isysroot", arch.sysroot]) configure = sh.Command(join(self.build_dir, "configure")) shprint(configure, diff --git a/recipes/python/__init__.pyc b/recipes/python/__init__.pyc index 2834000..ed3470c 100644 Binary files a/recipes/python/__init__.pyc and b/recipes/python/__init__.pyc differ diff --git a/toolchain.py b/toolchain.py index a858cae..6a8a9e5 100644 --- a/toolchain.py +++ b/toolchain.py @@ -37,6 +37,86 @@ class ChromeDownloader(FancyURLopener): urlretrieve = ChromeDownloader().retrieve +class Arch(object): + pass + + +class ArchSimulator(Arch): + sdk = "iphonesimulator" + arch = "i386" + triple = "i386-apple-darwin11" + version_min = "-miphoneos-version-min=5.1.1" + sysroot = sh.xcrun("--sdk", "iphonesimulator", "--show-sdk-path").strip() + + +class Arch64Simulator(Arch): + sdk = "iphonesimulator" + arch = "x86_64" + triple = "x86_64-apple-darwin13" + version_min = "-miphoneos-version-min=7.0" + sysroot = sh.xcrun("--sdk", "iphonesimulator", "--show-sdk-path").strip() + + +class ArchIOS(Arch): + sdk = "iphoneos" + arch = "armv7" + triple = "arm-apple-darwin11" + version_min = "-miphoneos-version-min=5.1.1" + sysroot = sh.xcrun("--sdk", "iphoneos", "--show-sdk-path").strip() + + +class Arch64IOS(Arch): + sdk = "iphoneos" + arch = "arm64" + triple = "aarch64-apple-darwin13" + version_min = "-miphoneos-version-min=7.0" + sysroot = sh.xcrun("--sdk", "iphoneos", "--show-sdk-path").strip() + + +class Graph(object): + # Taken from python-for-android/depsort + def __init__(self): + # `graph`: dict that maps each package to a set of its dependencies. + self.graph = {} + + def add(self, dependent, dependency): + """Add a dependency relationship to the graph""" + self.graph.setdefault(dependent, set()) + self.graph.setdefault(dependency, set()) + if dependent != dependency: + self.graph[dependent].add(dependency) + + def add_optional(self, dependent, dependency): + """Add an optional (ordering only) dependency relationship to the graph + + Only call this after all mandatory requirements are added + """ + if dependent in self.graph and dependency in self.graph: + self.add(dependent, dependency) + + def find_order(self): + """Do a topological sort on a dependency graph + + :Parameters: + :Returns: + iterator, sorted items form first to last + """ + graph = dict((k, set(v)) for k, v in self.graph.items()) + while graph: + # Find all items without a parent + leftmost = [l for l, s in graph.items() if not s] + if not leftmost: + raise ValueError('Dependency cycle detected! %s' % graph) + # If there is more than one, sort them for predictable order + leftmost.sort() + for result in leftmost: + # Yield and remove them from the graph + yield result + graph.pop(result) + for bset in graph.values(): + bset.discard(result) + + class Context(object): env = environ.copy() root_dir = None @@ -87,7 +167,7 @@ class Context(object): self.cache_dir = "{}/.cache".format(self.root_dir) self.dist_dir = "{}/dist".format(self.root_dir) self.install_dir = "{}/dist/root".format(self.root_dir) - self.archs = ("i386", "armv7", "armv7s", "arm64") + self.archs = (ArchSimulator, Arch64Simulator, ArchIOS, Arch64IOS) # path to some tools self.ccache = sh.which("ccache") @@ -128,6 +208,7 @@ class Context(object): class Recipe(object): version = None url = None + archs = [] depends = [] # API available for recipes @@ -238,15 +319,21 @@ class Recipe(object): @property def archive_fn(self): - if hasattr(self, "ext"): - ext = self.ext - else: - ext = basename(self.url).split(".", 1)[-1] - fn = "{}/{}.{}".format( + bfn = basename(self.url.format(version=self.version)) + fn = "{}/{}-{}".format( self.ctx.cache_dir, - self.name, ext) + self.name, bfn) return fn + @property + def filtered_archs(self): + for arch in self.ctx.archs: + if not self.archs or (arch.arch in self.archs): + yield arch + + def get_build_dir(self, arch): + return join(self.ctx.build_dir, arch, self.archive_root) + # Public Recipe API to be subclassed if needed def init_with_ctx(self, ctx): @@ -268,70 +355,118 @@ class Recipe(object): def extract(self): # recipe tmp directory - archive_root = self.get_archive_rootdir(self.archive_fn) - for arch in self.ctx.archs: - print("Extract {} for {}".format(self.name, arch)) - self.extract_arch(arch, archive_root) + for arch in self.filtered_archs: + print("Extract {} for {}".format(self.name, arch.arch)) + self.extract_arch(arch.arch) - def extract_arch(self, arch, archive_root): + def extract_arch(self, arch): build_dir = join(self.ctx.build_dir, arch) - if exists(join(build_dir, archive_root)): + if exists(join(build_dir, self.archive_root)): return ensure_dir(build_dir) self.extract_file(self.archive_fn, build_dir) def build_all(self): - archive_root = self.get_archive_rootdir(self.archive_fn) - for arch in self.ctx.archs: - self.build_dir = join(self.ctx.build_dir, arch, archive_root) + filtered_archs = list(self.filtered_archs) + print("Build {} for {} (filtered)".format( + self.name, + ", ".join([x.arch for x in filtered_archs]))) + for arch in self.filtered_archs: + self.build_dir = join(self.ctx.build_dir, arch.arch, self.archive_root) if self.has_marker("building"): print("Warning: {} build for {} has been incomplete".format( - self.name, arch)) + self.name, arch.arch)) print("Warning: deleting the build and restarting.") shutil.rmtree(self.build_dir) - self.extract_arch(arch, archive_root) + self.extract_arch(arch.arch) + + if self.has_marker("build_done"): + print("Build already done.") + continue + self.set_marker("building") chdir(self.build_dir) - print("Prebuild {} for {}".format(self.name, arch)) + print("Prebuild {} for {}".format(self.name, arch.arch)) self.prebuild_arch(arch) - print("Build {} for {}".format(self.name, arch)) + print("Build {} for {}".format(self.name, arch.arch)) self.build_arch(arch) - print("Postbuild {} for {}".format(self.name, arch)) + print("Postbuild {} for {}".format(self.name, arch.arch)) self.postbuild_arch(arch) self.delete_marker("building") + self.set_marker("build_done") + + name = self.name + if not name.startswith("lib"): + name = "lib{}".format(name) + static_fn = join(self.ctx.dist_dir, "lib", "{}.a".format(name)) + ensure_dir(dirname(static_fn)) + print("Assemble {} to {}".format(self.name, static_fn)) + self.assemble_to(static_fn) def prebuild_arch(self, arch): - prebuild = "prebuild_{}".format(arch) + prebuild = "prebuild_{}".format(arch.arch) if hasattr(self, prebuild): getattr(self, prebuild)() def build_arch(self, arch): - build = "build_{}".format(arch) + build = "build_{}".format(arch.arch) if hasattr(self, build): getattr(self, build)() def postbuild_arch(self, arch): - postbuild = "postbuild_{}".format(arch) + postbuild = "postbuild_{}".format(arch.arch) if hasattr(self, postbuild): getattr(self, postbuild)() + def assemble_to(self, filename): + return -def list_recipes(): - recipes_dir = join(dirname(__file__), "recipes") - for name in listdir(recipes_dir): - fn = join(recipes_dir, name) - if isdir(fn): - yield name + @classmethod + def list_recipes(cls): + recipes_dir = join(dirname(__file__), "recipes") + for name in listdir(recipes_dir): + fn = join(recipes_dir, name) + if isdir(fn): + yield name -def build_recipe(name, ctx): - mod = importlib.import_module("recipes.{}".format(name)) - recipe = mod.recipe - recipe.recipe_dir = join(ctx.root_dir, "recipes", name) - recipe.init_with_ctx(ctx) - recipe.execute() + @classmethod + def get_recipe(cls, name): + if not hasattr(cls, "recipes"): + cls.recipes = {} + if name in cls.recipes: + return cls.recipes[name] + mod = importlib.import_module("recipes.{}".format(name)) + recipe = mod.recipe + recipe.recipe_dir = join(ctx.root_dir, "recipes", name) + return recipe +def build_recipes(names, ctx): + # gather all the dependencies + print("Want to build {}".format(names)) + graph = Graph() + recipe_to_load = names + recipe_loaded = [] + while names: + name = recipe_to_load.pop(0) + if name in recipe_loaded: + continue + print("Load recipe {}".format(name)) + recipe = Recipe.get_recipe(name) + print("Recipe {} depends of {}".format(name, recipe.depends)) + for depend in recipe.depends: + graph.add(name, depend) + recipe_to_load += recipe.depends + recipe_loaded.append(name) + + build_order = list(graph.find_order()) + print("Build order is {}".format(build_order)) + for name in build_order: + recipe = Recipe.get_recipe(name) + recipe.init_with_ctx(ctx) + recipe.execute() + def ensure_dir(filename): if not exists(filename): makedirs(filename) @@ -343,5 +478,4 @@ if __name__ == "__main__": description='Compile Python and others extensions for iOS') args = parser.parse_args() ctx = Context() - print list(list_recipes()) - build_recipe("libffi", ctx) + build_recipes(["python"], ctx)