diff --git a/README.rst b/README.rst
index 12d76d4..cea324e 100644
--- a/README.rst
+++ b/README.rst
@@ -15,8 +15,6 @@ The toolchain supports:
These recipes are not ported to the new toolchain yet:
-- openssl
-- openssl-link
- lxml
@@ -56,6 +54,7 @@ You can list the available recipes and their versions with::
ios master
kivy ios-poly-arch
libffi 3.2.1
+ openssl 1.0.2e
pyobjus master
python 2.7.1
sdl2 iOS-improvements
@@ -67,6 +66,14 @@ Then, start the compilation with::
$ ./toolchain.py build kivy
+You can build recipes at the same time by adding them as parameters::
+
+ $ ./toolchain.py build openssl kivy
+
+Recipe builds can be removed via the clean command e.g.::
+
+ $ ./toolchain.py clean openssl
+
The Kivy recipe depends on several others, like the sdl* and python recipes.
These may in turn depend on others e.g. sdl2_ttf depends on freetype, etc.
You can think of it as follows: the kivy recipe will compile everything
@@ -76,12 +83,16 @@ Don't grab a coffee, just do diner. Compiling all the libraries for the first
time, 4x over (remember, 4 archs, 2 per platforms), will take time. (TODO:
provide a way to not compile for the simulator.).
+For a complete list of available commands, type::
+
+ $ ./toolchain.py
+
Create the Xcode project
------------------------
The `toolchain.py` can create the initial Xcode project for you::
- $ # ./toolchain.py create
+ $ ./toolchain.py create
$ ./toolchain.py create Touchtracer ~/code/kivy/examples/demo/touchtracer
Your app directory must contain a main.py. A directory named `-ios`
@@ -92,17 +103,34 @@ You can open the Xcode project using::
Then click on `Play`, and enjoy.
-.. notes::
+.. note::
Everytime you press `Play`, your application directory will be synced to
the `-ios/YourApp` directory. Don't make changes in the -ios
directory directly.
+Configuring your App
+--------------------
+
+When you first build your XCode project, a 'main.m' file is created in your
+XCode project folder. This file configures your environment variables and
+controls your application startup. You can edit this file to customize your
+launch environment.
+
+.. note::
+
+ If you wish to restrict your apps orientation, you should do this via
+ the 'export_orientation' function in 'main.m'. The XCode orientation
+ settings should be set to support all.
+
FAQ
---
Fatal error: "stdio.h" file not found
You need to install the Command line tools: `xcode-select --install`
+
+You must build with bitcode disabled (Xcode setting ENABLE_BITCODE should be No).
+ We don't support bitcode. You need to go to the project setting, and disable bitcode.
Support
-------
diff --git a/recipes/distribute/__init__.py b/recipes/distribute/__init__.py
new file mode 100644
index 0000000..fc82a11
--- /dev/null
+++ b/recipes/distribute/__init__.py
@@ -0,0 +1,21 @@
+from toolchain import PythonRecipe, shprint
+from os.path import join
+import sh, os
+
+class DistributeRecipe(PythonRecipe):
+ version = "0.7.3"
+ # url = "https://github.com/mitsuhiko/click/archive/{version}.zip"
+ url = "https://pypi.python.org/packages/source/d/distribute/distribute-{version}.zip"
+ depends = ["python"]
+
+ def install(self):
+ arch = list(self.filtered_archs)[0]
+ build_dir = self.get_build_dir(arch.arch)
+ os.chdir(build_dir)
+ hostpython = sh.Command(self.ctx.hostpython)
+ build_env = arch.get_env()
+ dest_dir = join(self.ctx.dist_dir, "root", "python")
+ build_env['PYTHONPATH'] = join(dest_dir, 'lib', 'python2.7', 'site-packages')
+ shprint(hostpython, "setup.py", "install", "--prefix", dest_dir, _env=build_env)
+
+recipe = DistributeRecipe()
diff --git a/recipes/ffpyplayer/__init__.py b/recipes/ffpyplayer/__init__.py
index 8dd2129..eae0580 100644
--- a/recipes/ffpyplayer/__init__.py
+++ b/recipes/ffpyplayer/__init__.py
@@ -4,7 +4,7 @@ import sh
class FFPyplayerRecipe(CythonRecipe):
- version = "master"
+ version = "v3.2"
url = "https://github.com/matham/ffpyplayer/archive/{version}.zip"
library = "libffpyplayer.a"
depends = ["python", "ffmpeg"]
diff --git a/recipes/host_setuptools/__init__.py b/recipes/host_setuptools/__init__.py
index 95e6ff9..ccd0ffe 100644
--- a/recipes/host_setuptools/__init__.py
+++ b/recipes/host_setuptools/__init__.py
@@ -8,14 +8,26 @@ import shutil
class HostSetuptools(Recipe):
depends = ["hostpython"]
- archs = 'i386'
+ archs = ["x86_64"]
url = ""
def prebuild_arch(self, arch):
hostpython = sh.Command(self.ctx.hostpython)
sh.curl("-O", "https://bootstrap.pypa.io/ez_setup.py")
- shprint(hostpython, "./ez_setup.py")
+ dest_dir = join(self.ctx.dist_dir, "root", "python")
+ build_env = arch.get_env()
+ build_env['PYTHONPATH'] = join(dest_dir, 'lib', 'python2.7', 'site-packages')
+ # shprint(hostpython, "./ez_setup.py", "--to-dir", dest_dir)
+ shprint(hostpython, "./ez_setup.py", _env=build_env)
+
+ # def install(self):
+ # arch = list(self.filtered_archs)[0]
+ # build_dir = self.get_build_dir(arch.arch)
+ # os.chdir(build_dir)
+ # hostpython = sh.Command(self.ctx.hostpython)
+ # build_env = arch.get_env()
+ # dest_dir = join(self.ctx.dist_dir, "root", "python")
+ # build_env['PYTHONPATH'] = join(dest_dir, 'lib', 'python2.7', 'site-packages')
+ # shprint(hostpython, "setup.py", "install", "--prefix", dest_dir, _env=build_env)
recipe = HostSetuptools()
-
-
diff --git a/recipes/hostpython/__init__.py b/recipes/hostpython/__init__.py
index b259759..e0e1588 100644
--- a/recipes/hostpython/__init__.py
+++ b/recipes/hostpython/__init__.py
@@ -48,7 +48,8 @@ class HostpythonRecipe(Recipe):
def build_x86_64(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"
+ ccache = (build_env["CCACHE"] + ' ') if 'CCACHE' in build_env else ''
+ build_env["CC"] = ccache + "clang -Qunused-arguments -fcolor-diagnostics"
build_env["LDFLAGS"] = " ".join([
"-lsqlite3",
"-lffi",
@@ -64,9 +65,9 @@ class HostpythonRecipe(Recipe):
"--disable-toolbox-glue",
"--without-gcc",
_env=build_env)
- shprint(sh.make, "-C", self.build_dir, "-j4", "python.exe", "Parser/pgen",
+ shprint(sh.make, "-C", self.build_dir, "-j4", "python", "Parser/pgen",
_env=build_env)
- shutil.move("python.exe", "hostpython")
+ shutil.move("python", "hostpython")
shutil.move("Parser/pgen", "Parser/hostpgen")
def install(self):
@@ -74,6 +75,11 @@ class HostpythonRecipe(Recipe):
build_env = arch.get_env()
build_dir = self.get_build_dir(arch.arch)
build_env["PATH"] = os.environ["PATH"]
+ # Compiling sometimes looks for Python-ast.py in the 'Python' i.s.o.
+ # the 'hostpython' folder. Create a symlink to fix. See issue #201
+ shprint(sh.ln, "-s",
+ join(build_dir, "hostpython"),
+ join(build_dir, "Python"))
shprint(sh.make,
"-C", build_dir,
"bininstall", "inclinstall",
diff --git a/recipes/ios/src/ios.pyx b/recipes/ios/src/ios.pyx
index 4583320..05e9831 100644
--- a/recipes/ios/src/ios.pyx
+++ b/recipes/ios/src/ios.pyx
@@ -171,3 +171,43 @@ def get_dpi():
'''Return the approximate DPI of the screen
'''
return ios_uiscreen_get_dpi()
+
+
+from pyobjus import autoclass, selector, protocol
+from pyobjus.protocols import protocols
+
+NSNotificationCenter = autoclass('NSNotificationCenter')
+
+protocols["KeyboardDelegates"] = {
+ 'keyboardWillShow': ('v16@0:4@8', "v32@0:8@16"),
+ 'keyboardDidHide': ('v16@0:4@8', "v32@0:8@16")}
+
+
+class IOSKeyboard(object):
+ '''Get listener for keyboard height.
+ '''
+
+ kheight = 0
+
+ def __init__(self, **kwargs):
+ super(IOSKeyboard, self).__init__()
+ NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, selector("keyboardWillShow"), "UIKeyboardWillShowNotification", None)
+ NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, selector("keyboardDidHide"), "UIKeyboardDidHideNotification", None)
+
+ @protocol('KeyboardDelegates')
+ def keyboardWillShow(self, notification):
+ self.kheight = get_scale() * notification.userInfo().objectForKey_(
+ 'UIKeyboardFrameEndUserInfoKey').CGRectValue().size.height
+ from kivy.core.window import Window
+ Window.trigger_keyboard_height()
+
+ @protocol('KeyboardDelegates')
+ def keyboardDidHide(self, notification):
+ self.kheight = 0
+ from kivy.core.window import Window
+ Window.trigger_keyboard_height()
+
+iOSKeyboard = IOSKeyboard()
+
+def get_kheight():
+ return iOSKeyboard.kheight
diff --git a/recipes/kivy/__init__.py b/recipes/kivy/__init__.py
index 248899a..c941f26 100644
--- a/recipes/kivy/__init__.py
+++ b/recipes/kivy/__init__.py
@@ -3,7 +3,7 @@ from os.path import join
class KivyRecipe(CythonRecipe):
- version = "1.9.0"
+ version = "1.9.1"
url = "https://github.com/kivy/kivy/archive/{version}.zip"
library = "libkivy.a"
depends = ["python", "sdl2", "sdl2_image", "sdl2_mixer", "sdl2_ttf", "ios"]
diff --git a/recipes/numpy/__init__.py b/recipes/numpy/__init__.py
index a30af2d..3ecdad4 100644
--- a/recipes/numpy/__init__.py
+++ b/recipes/numpy/__init__.py
@@ -9,6 +9,7 @@ class NumpyRecipe(CythonRecipe):
url = "http://pypi.python.org/packages/source/n/numpy/numpy-{version}.tar.gz"
library = "libnumpy.a"
libraries = ["libnpymath.a", "libnpysort.a"]
+ include_dir = "numpy/core/include"
depends = ["python"]
pbx_frameworks = ["Accelerate"]
cythonize = False
@@ -51,5 +52,3 @@ class NumpyRecipe(CythonRecipe):
shutil.rmtree(join(dest_dir, "tests"))
recipe = NumpyRecipe()
-
-
diff --git a/recipes/openssl/__init__.py b/recipes/openssl/__init__.py
index 07b7cf2..846ae84 100644
--- a/recipes/openssl/__init__.py
+++ b/recipes/openssl/__init__.py
@@ -10,7 +10,7 @@ arch_mapper = {'i386': 'darwin-i386-cc',
class OpensslRecipe(Recipe):
- version = "1.0.2d"
+ version = "1.0.2h"
url = "http://www.openssl.org/source/openssl-{version}.tar.gz"
libraries = ["libssl.a", "libcrypto.a"]
include_dir = "include"
diff --git a/recipes/pyyaml/__init__.py b/recipes/pyyaml/__init__.py
new file mode 100644
index 0000000..9f06bfa
--- /dev/null
+++ b/recipes/pyyaml/__init__.py
@@ -0,0 +1,21 @@
+# pure-python package, this can be removed when we'll support any python package
+import os
+import sh
+from toolchain import PythonRecipe, shprint
+
+class PyYamlRecipe(PythonRecipe):
+ version = "3.11"
+ url = "https://pypi.python.org/packages/source/P/PyYAML/PyYAML-{version}.tar.gz"
+ depends = ["python"]
+
+ def install(self):
+ arch = list(self.filtered_archs)[0]
+ build_dir = self.get_build_dir(arch.arch)
+ os.chdir(build_dir)
+ hostpython = sh.Command(self.ctx.hostpython)
+ build_env = arch.get_env()
+ dest_dir = os.path.join(self.ctx.dist_dir, "root", "python")
+ build_env['PYTHONPATH'] = os.path.join(dest_dir, 'lib', 'python2.7', 'site-packages')
+ shprint(hostpython, "setup.py", "install", "--prefix", dest_dir, _env=build_env)
+
+recipe = PyYamlRecipe()
diff --git a/recipes/sdl2/__init__.py b/recipes/sdl2/__init__.py
index bb016d8..f3040c3 100644
--- a/recipes/sdl2/__init__.py
+++ b/recipes/sdl2/__init__.py
@@ -19,9 +19,11 @@ class LibSDL2Recipe(Recipe):
self.set_marker("patched")
def build_arch(self, arch):
+ env = arch.get_env()
shprint(sh.xcodebuild,
"ONLY_ACTIVE_ARCH=NO",
"ARCHS={}".format(arch.arch),
+ "CC={}".format(env['CC']),
"-sdk", arch.sdk,
"-project", "Xcode-iOS/SDL/SDL.xcodeproj",
"-target", "libSDL",
diff --git a/toolchain.py b/toolchain.py
index 58bd2e0..f55a9d8 100755
--- a/toolchain.py
+++ b/toolchain.py
@@ -122,6 +122,7 @@ class Arch(object):
def __init__(self, ctx):
super(Arch, self).__init__()
self.ctx = ctx
+ self._ccsh = None
def __str__(self):
return self.arch
@@ -143,7 +144,33 @@ class Arch(object):
for d in self.ctx.include_dirs]
env = {}
- env["CC"] = sh.xcrun("-find", "-sdk", self.sdk, "clang").strip()
+ ccache = sh.which('ccache')
+ cc = sh.xcrun("-find", "-sdk", self.sdk, "clang").strip()
+ if ccache:
+ ccache = ccache.strip()
+ use_ccache = environ.get("USE_CCACHE", "1")
+ if use_ccache != '1':
+ env["CC"] = cc
+ else:
+ if not self._ccsh:
+ self._ccsh = ccsh = sh.mktemp().strip()
+ with open(ccsh, 'w') as f:
+ f.write('#!/bin/sh\n')
+ f.write(ccache + ' ' + cc + ' "$@"\n')
+ sh.chmod('+x', ccsh)
+ else:
+ ccsh = self._ccsh
+ env["USE_CCACHE"] = '1'
+ env["CCACHE"] = ccache
+ env["CC"] = ccsh
+
+ env.update({k: v for k, v in environ.items() if k.startswith('CCACHE_')})
+ env.setdefault('CCACHE_MAXSIZE', '10G')
+ env.setdefault('CCACHE_HARDLINK', 'true')
+ env.setdefault('CCACHE_SLOPPINESS', ('file_macro,time_macros,'
+ 'include_file_mtime,include_file_ctime,file_stat_matches'))
+ else:
+ env["CC"] = cc
env["AR"] = sh.xcrun("-find", "-sdk", self.sdk, "ar").strip()
env["LD"] = sh.xcrun("-find", "-sdk", self.sdk, "ld").strip()
env["OTHER_CFLAGS"] = " ".join(include_dirs)
@@ -418,6 +445,8 @@ class Recipe(object):
print('This is usually caused by a corrupt download. The file'
' will be removed and re-downloaded on the next run.')
remove(filename)
+ return
+
root = archive.next().path.split("/")
return root[0]
elif filename.endswith(".zip"):
@@ -544,7 +573,7 @@ class Recipe(object):
def archive_root(self):
key = "{}.archive_root".format(self.name)
value = self.ctx.state.get(key)
- if not key:
+ if not value:
value = self.get_archive_rootdir(self.archive_fn)
self.ctx.state[key] = value
return value
@@ -781,11 +810,22 @@ class Recipe(object):
def get_recipe(cls, name, ctx):
if not hasattr(cls, "recipes"):
cls.recipes = {}
+
+ if '==' in name:
+ name, version = name.split('==')
+ else:
+ version = None
+
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)
+ recipe = cls.recipes[name]
+ else:
+ mod = importlib.import_module("recipes.{}".format(name))
+ recipe = mod.recipe
+ recipe.recipe_dir = join(ctx.root_dir, "recipes", name)
+
+ if version:
+ recipe.version = version
+
return recipe
@@ -795,12 +835,12 @@ class PythonRecipe(Recipe):
self.install_python_package()
self.reduce_python_package()
- def remove_junk(self, d):
- exts = ["pyc", "py", "so.lib", "so.o", "sh"]
+ @staticmethod
+ def remove_junk(d):
+ exts = [".pyc", ".py", ".so.lib", ".so.o", ".sh"]
for root, dirnames, filenames in walk(d):
for fn in filenames:
- ext = fn.rsplit(".", 1)[-1]
- if ext in exts:
+ if any([fn.endswith(ext) for ext in exts]):
unlink(join(root, fn))
def install_python_package(self, name=None, env=None, is_dir=True):
@@ -1020,8 +1060,9 @@ if __name__ == "__main__":
usage="""toolchain []
Available commands:
- build Build a specific recipe
- clean Clean the build
+ build Build a recipe (compile a library for the required target
+ architecture)
+ clean Clean the build of the specified recipe
distclean Clean the build and the result
recipes List all the available recipes
status List all the recipes and their build status
@@ -1183,6 +1224,36 @@ Xcode:
print("--")
print("Project {} updated".format(filename))
+ def pip(self):
+ ctx = Context()
+ for recipe in Recipe.list_recipes():
+ key = "{}.build_all".format(recipe)
+ if key not in ctx.state:
+ continue
+ recipe = Recipe.get_recipe(recipe, ctx)
+ recipe.init_with_ctx(ctx)
+ print(ctx.site_packages_dir)
+ if not hasattr(ctx, "site_packages_dir"):
+ print("ERROR: python must be compiled before using pip")
+ sys.exit(1)
+
+ pip_env = {
+ "CC": "/bin/false",
+ "CXX": "/bin/false",
+ "PYTHONPATH": ctx.site_packages_dir,
+ "PYTHONOPTIMIZE": "2",
+ "PIP_INSTALL_TARGET": ctx.site_packages_dir
+ }
+ print pip_env
+ pip_path = sh.which("pip")
+ args = [pip_path] + sys.argv[2:]
+ if not pip_path:
+ print("ERROR: pip not found")
+ sys.exit(1)
+ import os
+ print("-- execute pip with: {}".format(args))
+ os.execve(pip_path, args, pip_env)
+
def launchimage(self):
import xcassets
self._xcassets("LaunchImage", xcassets.launchimage)
@@ -1191,6 +1262,21 @@ Xcode:
import xcassets
self._xcassets("Icon", xcassets.icon)
+ def xcode(self):
+ parser = argparse.ArgumentParser(description="Open the xcode project")
+ parser.add_argument("filename", help="Path to your project or xcodeproj")
+ args = parser.parse_args(sys.argv[2:])
+ filename = args.filename
+ if not filename.endswith(".xcodeproj"):
+ # try to find the xcodeproj
+ from glob import glob
+ xcodeproj = glob(join(filename, "*.xcodeproj"))
+ if not xcodeproj:
+ print("ERROR: Unable to find a xcodeproj in {}".format(filename))
+ sys.exit(1)
+ filename = xcodeproj[0]
+ sh.open(filename)
+
def _xcassets(self, title, command):
parser = argparse.ArgumentParser(
description="Generate {} for your project".format(title))
diff --git a/tools/templates/{{ cookiecutter.project_name }}-ios/main.m b/tools/templates/{{ cookiecutter.project_name }}-ios/main.m
index 017a930..99f2325 100644
--- a/tools/templates/{{ cookiecutter.project_name }}-ios/main.m
+++ b/tools/templates/{{ cookiecutter.project_name }}-ios/main.m
@@ -83,12 +83,25 @@ int main(int argc, char *argv[]) {
return ret;
}
-// This method read available orientations from the Info.plist, and share them
-// in an environment variable. Kivy will automatically set the orientation
-// according to this environment value, if exist.
+// This method reads the available orientations from the Info.plist file and
+// shares them via an environment variable. Kivy will automatically set the
+// orientation according to this environment value, if it exists. To restrict
+// the allowed orientation, please see the comments inside.
void export_orientation() {
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSArray *orientations = [info objectForKey:@"UISupportedInterfaceOrientations"];
+
+ // Orientation restrictions
+ // ========================
+ // Comment or uncomment blocks 1-3 in order the limit orientation support
+
+ // 1. Landscape only
+ // NSString *result = [[NSString alloc] initWithString:@"KIVY_ORIENTATION=LandscapeLeft LandscapeRight"];
+
+ // 2. Portrait only
+ // NSString *result = [[NSString alloc] initWithString:@"KIVY_ORIENTATION=Portrait PortraitUpsideDown"];
+
+ // 3. All orientations
NSString *result = [[NSString alloc] initWithString:@"KIVY_ORIENTATION="];
for (int i = 0; i < [orientations count]; i++) {
NSString *item = [orientations objectAtIndex:i];
@@ -97,6 +110,7 @@ void export_orientation() {
result = [result stringByAppendingString:@" "];
result = [result stringByAppendingString:item];
}
+ // ========================
putenv((char *)[result UTF8String]);
NSLog(@"Available orientation: %@", result);