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: These recipes are not ported to the new toolchain yet:
- openssl
- openssl-link
- lxml - lxml
@ -56,6 +54,7 @@ You can list the available recipes and their versions with::
ios master ios master
kivy ios-poly-arch kivy ios-poly-arch
libffi 3.2.1 libffi 3.2.1
openssl 1.0.2e
pyobjus master pyobjus master
python 2.7.1 python 2.7.1
sdl2 iOS-improvements sdl2 iOS-improvements
@ -67,6 +66,14 @@ Then, start the compilation with::
$ ./toolchain.py build kivy $ ./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. 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. 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 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: time, 4x over (remember, 4 archs, 2 per platforms), will take time. (TODO:
provide a way to not compile for the simulator.). provide a way to not compile for the simulator.).
For a complete list of available commands, type::
$ ./toolchain.py
Create the Xcode project Create the Xcode project
------------------------ ------------------------
The `toolchain.py` can create the initial Xcode project for you:: 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 $ ./toolchain.py create Touchtracer ~/code/kivy/examples/demo/touchtracer
Your app directory must contain a main.py. A directory named `<title>-ios` 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. Then click on `Play`, and enjoy.
.. notes:: .. note::
Everytime you press `Play`, your application directory will be synced to Everytime you press `Play`, your application directory will be synced to
the `<title>-ios/YourApp` directory. Don't make changes in the -ios the `<title>-ios/YourApp` directory. Don't make changes in the -ios
directory directly. 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 FAQ
--- ---
Fatal error: "stdio.h" file not found Fatal error: "stdio.h" file not found
You need to install the Command line tools: `xcode-select --install` 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 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): class FFPyplayerRecipe(CythonRecipe):
version = "master" version = "v3.2"
url = "https://github.com/matham/ffpyplayer/archive/{version}.zip" url = "https://github.com/matham/ffpyplayer/archive/{version}.zip"
library = "libffpyplayer.a" library = "libffpyplayer.a"
depends = ["python", "ffmpeg"] depends = ["python", "ffmpeg"]

View file

@ -8,14 +8,26 @@ import shutil
class HostSetuptools(Recipe): class HostSetuptools(Recipe):
depends = ["hostpython"] depends = ["hostpython"]
archs = 'i386' archs = ["x86_64"]
url = "" url = ""
def prebuild_arch(self, arch): def prebuild_arch(self, arch):
hostpython = sh.Command(self.ctx.hostpython) hostpython = sh.Command(self.ctx.hostpython)
sh.curl("-O", "https://bootstrap.pypa.io/ez_setup.py") 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() recipe = HostSetuptools()

View file

@ -48,7 +48,8 @@ class HostpythonRecipe(Recipe):
def build_x86_64(self): def build_x86_64(self):
sdk_path = sh.xcrun("--sdk", "macosx", "--show-sdk-path").strip() sdk_path = sh.xcrun("--sdk", "macosx", "--show-sdk-path").strip()
build_env = self.ctx.env.copy() 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([ build_env["LDFLAGS"] = " ".join([
"-lsqlite3", "-lsqlite3",
"-lffi", "-lffi",
@ -64,9 +65,9 @@ class HostpythonRecipe(Recipe):
"--disable-toolbox-glue", "--disable-toolbox-glue",
"--without-gcc", "--without-gcc",
_env=build_env) _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) _env=build_env)
shutil.move("python.exe", "hostpython") shutil.move("python", "hostpython")
shutil.move("Parser/pgen", "Parser/hostpgen") shutil.move("Parser/pgen", "Parser/hostpgen")
def install(self): def install(self):
@ -74,6 +75,11 @@ class HostpythonRecipe(Recipe):
build_env = arch.get_env() build_env = arch.get_env()
build_dir = self.get_build_dir(arch.arch) build_dir = self.get_build_dir(arch.arch)
build_env["PATH"] = os.environ["PATH"] 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, shprint(sh.make,
"-C", build_dir, "-C", build_dir,
"bininstall", "inclinstall", "bininstall", "inclinstall",

View file

@ -171,3 +171,43 @@ def get_dpi():
'''Return the approximate DPI of the screen '''Return the approximate DPI of the screen
''' '''
return ios_uiscreen_get_dpi() 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): class KivyRecipe(CythonRecipe):
version = "1.9.0" version = "1.9.1"
url = "https://github.com/kivy/kivy/archive/{version}.zip" url = "https://github.com/kivy/kivy/archive/{version}.zip"
library = "libkivy.a" library = "libkivy.a"
depends = ["python", "sdl2", "sdl2_image", "sdl2_mixer", "sdl2_ttf", "ios"] 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" url = "http://pypi.python.org/packages/source/n/numpy/numpy-{version}.tar.gz"
library = "libnumpy.a" library = "libnumpy.a"
libraries = ["libnpymath.a", "libnpysort.a"] libraries = ["libnpymath.a", "libnpysort.a"]
include_dir = "numpy/core/include"
depends = ["python"] depends = ["python"]
pbx_frameworks = ["Accelerate"] pbx_frameworks = ["Accelerate"]
cythonize = False cythonize = False
@ -51,5 +52,3 @@ class NumpyRecipe(CythonRecipe):
shutil.rmtree(join(dest_dir, "tests")) shutil.rmtree(join(dest_dir, "tests"))
recipe = NumpyRecipe() recipe = NumpyRecipe()

View file

@ -10,7 +10,7 @@ arch_mapper = {'i386': 'darwin-i386-cc',
class OpensslRecipe(Recipe): class OpensslRecipe(Recipe):
version = "1.0.2d" version = "1.0.2h"
url = "http://www.openssl.org/source/openssl-{version}.tar.gz" url = "http://www.openssl.org/source/openssl-{version}.tar.gz"
libraries = ["libssl.a", "libcrypto.a"] libraries = ["libssl.a", "libcrypto.a"]
include_dir = "include" 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") self.set_marker("patched")
def build_arch(self, arch): def build_arch(self, arch):
env = arch.get_env()
shprint(sh.xcodebuild, shprint(sh.xcodebuild,
"ONLY_ACTIVE_ARCH=NO", "ONLY_ACTIVE_ARCH=NO",
"ARCHS={}".format(arch.arch), "ARCHS={}".format(arch.arch),
"CC={}".format(env['CC']),
"-sdk", arch.sdk, "-sdk", arch.sdk,
"-project", "Xcode-iOS/SDL/SDL.xcodeproj", "-project", "Xcode-iOS/SDL/SDL.xcodeproj",
"-target", "libSDL", "-target", "libSDL",

View file

@ -122,6 +122,7 @@ class Arch(object):
def __init__(self, ctx): def __init__(self, ctx):
super(Arch, self).__init__() super(Arch, self).__init__()
self.ctx = ctx self.ctx = ctx
self._ccsh = None
def __str__(self): def __str__(self):
return self.arch return self.arch
@ -143,7 +144,33 @@ class Arch(object):
for d in self.ctx.include_dirs] for d in self.ctx.include_dirs]
env = {} 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["AR"] = sh.xcrun("-find", "-sdk", self.sdk, "ar").strip()
env["LD"] = sh.xcrun("-find", "-sdk", self.sdk, "ld").strip() env["LD"] = sh.xcrun("-find", "-sdk", self.sdk, "ld").strip()
env["OTHER_CFLAGS"] = " ".join(include_dirs) env["OTHER_CFLAGS"] = " ".join(include_dirs)
@ -418,6 +445,8 @@ class Recipe(object):
print('This is usually caused by a corrupt download. The file' print('This is usually caused by a corrupt download. The file'
' will be removed and re-downloaded on the next run.') ' will be removed and re-downloaded on the next run.')
remove(filename) remove(filename)
return
root = archive.next().path.split("/") root = archive.next().path.split("/")
return root[0] return root[0]
elif filename.endswith(".zip"): elif filename.endswith(".zip"):
@ -544,7 +573,7 @@ class Recipe(object):
def archive_root(self): def archive_root(self):
key = "{}.archive_root".format(self.name) key = "{}.archive_root".format(self.name)
value = self.ctx.state.get(key) value = self.ctx.state.get(key)
if not key: if not value:
value = self.get_archive_rootdir(self.archive_fn) value = self.get_archive_rootdir(self.archive_fn)
self.ctx.state[key] = value self.ctx.state[key] = value
return value return value
@ -781,11 +810,22 @@ class Recipe(object):
def get_recipe(cls, name, ctx): def get_recipe(cls, name, ctx):
if not hasattr(cls, "recipes"): if not hasattr(cls, "recipes"):
cls.recipes = {} cls.recipes = {}
if '==' in name:
name, version = name.split('==')
else:
version = None
if name in cls.recipes: if name in cls.recipes:
return cls.recipes[name] recipe = cls.recipes[name]
mod = importlib.import_module("recipes.{}".format(name)) else:
recipe = mod.recipe mod = importlib.import_module("recipes.{}".format(name))
recipe.recipe_dir = join(ctx.root_dir, "recipes", name) recipe = mod.recipe
recipe.recipe_dir = join(ctx.root_dir, "recipes", name)
if version:
recipe.version = version
return recipe return recipe
@ -795,12 +835,12 @@ class PythonRecipe(Recipe):
self.install_python_package() self.install_python_package()
self.reduce_python_package() self.reduce_python_package()
def remove_junk(self, d): @staticmethod
exts = ["pyc", "py", "so.lib", "so.o", "sh"] def remove_junk(d):
exts = [".pyc", ".py", ".so.lib", ".so.o", ".sh"]
for root, dirnames, filenames in walk(d): for root, dirnames, filenames in walk(d):
for fn in filenames: for fn in filenames:
ext = fn.rsplit(".", 1)[-1] if any([fn.endswith(ext) for ext in exts]):
if ext in exts:
unlink(join(root, fn)) unlink(join(root, fn))
def install_python_package(self, name=None, env=None, is_dir=True): def install_python_package(self, name=None, env=None, is_dir=True):
@ -1020,8 +1060,9 @@ if __name__ == "__main__":
usage="""toolchain <command> [<args>] usage="""toolchain <command> [<args>]
Available commands: Available commands:
build Build a specific recipe build Build a recipe (compile a library for the required target
clean Clean the build architecture)
clean Clean the build of the specified recipe
distclean Clean the build and the result distclean Clean the build and the result
recipes List all the available recipes recipes List all the available recipes
status List all the recipes and their build status status List all the recipes and their build status
@ -1183,6 +1224,36 @@ Xcode:
print("--") print("--")
print("Project {} updated".format(filename)) 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): def launchimage(self):
import xcassets import xcassets
self._xcassets("LaunchImage", xcassets.launchimage) self._xcassets("LaunchImage", xcassets.launchimage)
@ -1191,6 +1262,21 @@ Xcode:
import xcassets import xcassets
self._xcassets("Icon", xcassets.icon) 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): def _xcassets(self, title, command):
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Generate {} for your project".format(title)) description="Generate {} for your project".format(title))

View file

@ -83,12 +83,25 @@ int main(int argc, char *argv[]) {
return ret; return ret;
} }
// This method read available orientations from the Info.plist, and share them // This method reads the available orientations from the Info.plist file and
// in an environment variable. Kivy will automatically set the orientation // shares them via an environment variable. Kivy will automatically set the
// according to this environment value, if exist. // orientation according to this environment value, if it exists. To restrict
// the allowed orientation, please see the comments inside.
void export_orientation() { void export_orientation() {
NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSArray *orientations = [info objectForKey:@"UISupportedInterfaceOrientations"]; 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="]; NSString *result = [[NSString alloc] initWithString:@"KIVY_ORIENTATION="];
for (int i = 0; i < [orientations count]; i++) { for (int i = 0; i < [orientations count]; i++) {
NSString *item = [orientations objectAtIndex:i]; NSString *item = [orientations objectAtIndex:i];
@ -97,6 +110,7 @@ void export_orientation() {
result = [result stringByAppendingString:@" "]; result = [result stringByAppendingString:@" "];
result = [result stringByAppendingString:item]; result = [result stringByAppendingString:item];
} }
// ========================
putenv((char *)[result UTF8String]); putenv((char *)[result UTF8String]);
NSLog(@"Available orientation: %@", result); NSLog(@"Available orientation: %@", result);