Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
fcae0e45bc
13 changed files with 260 additions and 31 deletions
36
README.rst
36
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 <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,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 `<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
|
||||
-------
|
||||
|
|
21
recipes/distribute/__init__.py
Normal file
21
recipes/distribute/__init__.py
Normal 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()
|
|
@ -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"]
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
21
recipes/pyyaml/__init__.py
Normal file
21
recipes/pyyaml/__init__.py
Normal 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()
|
|
@ -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",
|
||||
|
|
110
toolchain.py
110
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 <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))
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue