Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Liu Chao 2016-07-29 15:14:27 +08:00
commit fcae0e45bc
13 changed files with 260 additions and 31 deletions

View file

@ -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 <title> <app_directory>
$ ./toolchain.py create <title> <app_directory>
$ ./toolchain.py create Touchtracer ~/code/kivy/examples/demo/touchtracer
Your app directory must contain a main.py. A directory named `<title>-ios`
@ -92,18 +103,35 @@ 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 `<title>-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
-------

View file

@ -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()

View file

@ -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"]

View file

@ -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()

View file

@ -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",

View file

@ -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

View file

@ -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"]

View file

@ -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()

View file

@ -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"

View file

@ -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()

View file

@ -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",

View file

@ -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]
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 <command> [<args>]
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))

View file

@ -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);