Merge pull request from AndreMiras/feature/top_level_toolchaincl

Takes ToolchainCL definition outside the main
This commit is contained in:
Andre Miras 2020-05-06 00:19:15 +02:00 committed by GitHub
commit 3974a1e701
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -6,6 +6,7 @@ Tool for compiling iOS toolchain
This tool intend to replace all the previous tools/ in shell script. This tool intend to replace all the previous tools/ in shell script.
""" """
import argparse
import sys import sys
from sys import stdout from sys import stdout
from os.path import join, dirname, realpath, exists, isdir, basename, expanduser from os.path import join, dirname, realpath, exists, isdir, basename, expanduser
@ -1248,310 +1249,309 @@ def update_pbxproj(filename, pbx_frameworks=None):
project.save() project.save()
def main(): class ToolchainCL(object):
import argparse def __init__(self):
parser = argparse.ArgumentParser(
class ToolchainCL(object): description="Tool for managing the iOS / Python toolchain",
def __init__(self): usage="""toolchain <command> [<args>]
parser = argparse.ArgumentParser(
description="Tool for managing the iOS / Python toolchain",
usage="""toolchain <command> [<args>]
Available commands: Available commands:
build Build a recipe (compile a library for the required target build Build a recipe (compile a library for the required target
architecture) architecture)
clean Clean the build of the specified recipe 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
build_info Display the current build context and Architecture info build_info Display the current build context and Architecture info
Xcode: Xcode:
create Create a new xcode project create Create a new xcode project
update Update an existing xcode project (frameworks, libraries..) update Update an existing xcode project (frameworks, libraries..)
launchimage Create Launch images for your xcode project launchimage Create Launch images for your xcode project
icon Create Icons for your xcode project icon Create Icons for your xcode project
pip Install a pip dependency into the distribution pip Install a pip dependency into the distribution
""") """)
parser.add_argument("command", help="Command to run") parser.add_argument("command", help="Command to run")
args = parser.parse_args(sys.argv[1:2]) args = parser.parse_args(sys.argv[1:2])
if not hasattr(self, args.command): if not hasattr(self, args.command):
print('Unrecognized command') print('Unrecognized command')
parser.print_help() parser.print_help()
exit(1) exit(1)
getattr(self, args.command)() getattr(self, args.command)()
def build(self): def build(self):
ctx = Context() ctx = Context()
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Build the toolchain") description="Build the toolchain")
parser.add_argument("recipe", nargs="+", help="Recipe to compile") parser.add_argument("recipe", nargs="+", help="Recipe to compile")
parser.add_argument("--arch", action="append", parser.add_argument("--arch", action="append",
help="Restrict compilation to this arch") help="Restrict compilation to this arch")
parser.add_argument("--concurrency", type=int, default=ctx.num_cores, parser.add_argument("--concurrency", type=int, default=ctx.num_cores,
help="number of concurrent build processes (where supported)") help="number of concurrent build processes (where supported)")
parser.add_argument("--no-pigz", action="store_true", default=not bool(ctx.use_pigz), parser.add_argument("--no-pigz", action="store_true", default=not bool(ctx.use_pigz),
help="do not use pigz for gzip decompression") help="do not use pigz for gzip decompression")
parser.add_argument("--no-pbzip2", action="store_true", default=not bool(ctx.use_pbzip2), parser.add_argument("--no-pbzip2", action="store_true", default=not bool(ctx.use_pbzip2),
help="do not use pbzip2 for bzip2 decompression") help="do not use pbzip2 for bzip2 decompression")
args = parser.parse_args(sys.argv[2:]) args = parser.parse_args(sys.argv[2:])
if args.arch: if args.arch:
if len(args.arch) == 1: if len(args.arch) == 1:
archs = args.arch[0].split() archs = args.arch[0].split()
else:
archs = args.arch
available_archs = [arch.arch for arch in ctx.archs]
for arch in archs[:]:
if arch not in available_archs:
logger.error("Architecture {} invalid".format(arch))
archs.remove(arch)
continue
ctx.archs = [arch for arch in ctx.archs if arch.arch in archs]
logger.info("Architectures restricted to: {}".format(archs))
ctx.num_cores = args.concurrency
if args.no_pigz:
ctx.use_pigz = False
if args.no_pbzip2:
ctx.use_pbzip2 = False
ctx.use_pigz = ctx.use_pbzip2
logger.info("Building with {} processes, where supported".format(ctx.num_cores))
if ctx.use_pigz:
logger.info("Using pigz to decompress gzip data")
if ctx.use_pbzip2:
logger.info("Using pbzip2 to decompress bzip2 data")
build_recipes(args.recipe, ctx)
def recipes(self):
parser = argparse.ArgumentParser(
description="List all the available recipes")
parser.add_argument(
"--compact", action="store_true",
help="Produce a compact list suitable for scripting")
args = parser.parse_args(sys.argv[2:])
if args.compact:
print(" ".join(list(Recipe.list_recipes())))
else: else:
ctx = Context() archs = args.arch
for name in Recipe.list_recipes(): available_archs = [arch.arch for arch in ctx.archs]
try: for arch in archs[:]:
recipe = Recipe.get_recipe(name, ctx) if arch not in available_archs:
print("{recipe.name:<12} {recipe.version:<8}".format(recipe=recipe)) logger.error("Architecture {} invalid".format(arch))
archs.remove(arch)
except Exception:
pass
def clean(self):
parser = argparse.ArgumentParser(
description="Clean the build")
parser.add_argument("recipe", nargs="*", help="Recipe to clean")
args = parser.parse_args(sys.argv[2:])
ctx = Context()
if args.recipe:
for recipe in args.recipe:
logger.info("Cleaning {} build".format(recipe))
ctx.state.remove_all("{}.".format(recipe))
build_dir = join(ctx.build_dir, recipe)
shutil.rmtree(build_dir, ignore_errors=True)
else:
logger.info("Delete build directory")
shutil.rmtree(ctx.build_dir, ignore_errors=True)
def distclean(self):
ctx = Context()
shutil.rmtree(ctx.build_dir, ignore_errors=True)
shutil.rmtree(ctx.dist_dir, ignore_errors=True)
shutil.rmtree(ctx.cache_dir, ignore_errors=True)
def status(self):
ctx = Context()
for recipe in Recipe.list_recipes():
key = "{}.build_all".format(recipe)
keytime = "{}.build_all.at".format(recipe)
if key in ctx.state:
status = "Build OK (built at {})".format(ctx.state[keytime])
else:
status = "Not built"
print("{:<12} - {}".format(
recipe, status))
def create(self):
parser = argparse.ArgumentParser(
description="Create a new xcode project")
parser.add_argument("name", help="Name of your project")
parser.add_argument("directory", help="Directory where your project lives")
parser.add_argument("--add-framework", action="append", help="Additional Frameworks to include with this project")
args = parser.parse_args(sys.argv[2:])
from cookiecutter.main import cookiecutter
ctx = Context()
ensure_recipes_loaded(ctx)
if not hasattr(ctx, "python_ver"):
logger.error("No python recipes compiled!")
logger.error("You must have compiled at least python2 or")
logger.error("python3 recipes to be able to create a project.")
sys.exit(1)
template_dir = join(curdir, "tools", "templates")
context = {
"title": args.name,
"project_name": args.name.lower(),
"domain_name": "org.kivy.{}".format(args.name.lower()),
"kivy_dir": dirname(dirname(realpath(__file__))),
"project_dir": realpath(args.directory),
"version": "1.0.0",
"dist_dir": ctx.dist_dir,
"python_version": ctx.python_ver,
"python_major": ctx.python_major
}
cookiecutter(template_dir, no_input=True, extra_context=context)
filename = join(
getcwd(),
"{}-ios".format(args.name.lower()),
"{}.xcodeproj".format(args.name.lower()),
"project.pbxproj")
update_pbxproj(filename, pbx_frameworks=args.add_framework)
print("--")
print("Project directory : {}-ios".format(
args.name.lower()))
print("XCode project : {0}-ios/{0}.xcodeproj".format(
args.name.lower()))
def update(self):
parser = argparse.ArgumentParser(
description="Update an existing xcode project")
parser.add_argument("filename", help="Path to your project or xcodeproj")
parser.add_argument("--add-framework", action="append", help="Additional Frameworks to include with this project")
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:
logger.error("Unable to find a xcodeproj in {}".format(filename))
sys.exit(1)
filename = xcodeproj[0]
filename = join(filename, "project.pbxproj")
if not exists(filename):
logger.error("{} not found".format(filename))
sys.exit(1)
update_pbxproj(filename, pbx_frameworks=args.add_framework)
print("--")
print("Project {} updated".format(filename))
def build_info(self):
ctx = Context()
print("Build Context")
print("-------------")
for attr in dir(ctx):
if not attr.startswith("_"):
if not callable(attr) and attr != 'archs':
print("{}: {}".format(attr, pformat(getattr(ctx, attr))))
for arch in ctx.archs:
ul = '-' * (len(str(arch))+6)
print("\narch: {}\n{}".format(str(arch), ul))
for attr in dir(arch):
if not attr.startswith("_"):
if not callable(attr) and attr not in ['arch', 'ctx', 'get_env']:
print("{}: {}".format(attr, pformat(getattr(arch, attr))))
env = arch.get_env()
print("env ({}): {}".format(arch, pformat(env)))
def pip3(self):
self.pip()
def pip(self):
ctx = Context()
for recipe in Recipe.list_recipes():
key = "{}.build_all".format(recipe)
if key not in ctx.state:
continue continue
recipe = Recipe.get_recipe(recipe, ctx) ctx.archs = [arch for arch in ctx.archs if arch.arch in archs]
recipe.init_with_ctx(ctx) logger.info("Architectures restricted to: {}".format(archs))
if not hasattr(ctx, "site_packages_dir"): ctx.num_cores = args.concurrency
logger.error("python must be compiled before using pip") if args.no_pigz:
sys.exit(1) ctx.use_pigz = False
if args.no_pbzip2:
ctx.use_pbzip2 = False
ctx.use_pigz = ctx.use_pbzip2
logger.info("Building with {} processes, where supported".format(ctx.num_cores))
if ctx.use_pigz:
logger.info("Using pigz to decompress gzip data")
if ctx.use_pbzip2:
logger.info("Using pbzip2 to decompress bzip2 data")
build_recipes(args.recipe, ctx)
pip_env = { def recipes(self):
"CC": "/bin/false", parser = argparse.ArgumentParser(
"CXX": "/bin/false", description="List all the available recipes")
"PYTHONPATH": ctx.site_packages_dir, parser.add_argument(
"PYTHONOPTIMIZE": "2", "--compact", action="store_true",
# "PIP_INSTALL_TARGET": ctx.site_packages_dir help="Produce a compact list suitable for scripting")
} args = parser.parse_args(sys.argv[2:])
pip_path = join(ctx.dist_dir, 'hostpython3', 'bin', 'pip3')
pip_args = [] if args.compact:
if len(sys.argv) > 2 and sys.argv[2] == "install": print(" ".join(list(Recipe.list_recipes())))
pip_args = ["--isolated", "--ignore-installed", "--prefix", ctx.python_prefix] else:
args = [pip_path] + [sys.argv[2]] + pip_args + sys.argv[3:] ctx = Context()
for name in Recipe.list_recipes():
try:
recipe = Recipe.get_recipe(name, ctx)
print("{recipe.name:<12} {recipe.version:<8}".format(recipe=recipe))
except Exception:
pass
def clean(self):
parser = argparse.ArgumentParser(
description="Clean the build")
parser.add_argument("recipe", nargs="*", help="Recipe to clean")
args = parser.parse_args(sys.argv[2:])
ctx = Context()
if args.recipe:
for recipe in args.recipe:
logger.info("Cleaning {} build".format(recipe))
ctx.state.remove_all("{}.".format(recipe))
build_dir = join(ctx.build_dir, recipe)
shutil.rmtree(build_dir, ignore_errors=True)
else:
logger.info("Delete build directory")
shutil.rmtree(ctx.build_dir, ignore_errors=True)
def distclean(self):
ctx = Context()
shutil.rmtree(ctx.build_dir, ignore_errors=True)
shutil.rmtree(ctx.dist_dir, ignore_errors=True)
shutil.rmtree(ctx.cache_dir, ignore_errors=True)
def status(self):
ctx = Context()
for recipe in Recipe.list_recipes():
key = "{}.build_all".format(recipe)
keytime = "{}.build_all.at".format(recipe)
if key in ctx.state:
status = "Build OK (built at {})".format(ctx.state[keytime])
else: else:
args = [pip_path] + pip_args + sys.argv[2:] status = "Not built"
print("{:<12} - {}".format(
recipe, status))
import os def create(self):
logger.error("Executing pip with: {}".format(args)) parser = argparse.ArgumentParser(
os.execve(pip_path, args, pip_env) description="Create a new xcode project")
parser.add_argument("name", help="Name of your project")
parser.add_argument("directory", help="Directory where your project lives")
parser.add_argument("--add-framework", action="append", help="Additional Frameworks to include with this project")
args = parser.parse_args(sys.argv[2:])
def launchimage(self): from cookiecutter.main import cookiecutter
import xcassets ctx = Context()
self._xcassets("LaunchImage", xcassets.launchimage) ensure_recipes_loaded(ctx)
def icon(self): if not hasattr(ctx, "python_ver"):
import xcassets logger.error("No python recipes compiled!")
self._xcassets("Icon", xcassets.icon) logger.error("You must have compiled at least python2 or")
logger.error("python3 recipes to be able to create a project.")
sys.exit(1)
def xcode(self): template_dir = join(curdir, "tools", "templates")
parser = argparse.ArgumentParser(description="Open the xcode project") context = {
parser.add_argument("filename", help="Path to your project or xcodeproj") "title": args.name,
args = parser.parse_args(sys.argv[2:]) "project_name": args.name.lower(),
filename = args.filename "domain_name": "org.kivy.{}".format(args.name.lower()),
if not filename.endswith(".xcodeproj"): "kivy_dir": dirname(dirname(realpath(__file__))),
# try to find the xcodeproj "project_dir": realpath(args.directory),
from glob import glob "version": "1.0.0",
xcodeproj = glob(join(filename, "*.xcodeproj")) "dist_dir": ctx.dist_dir,
if not xcodeproj: "python_version": ctx.python_ver,
logger.error("Unable to find a xcodeproj in {}".format(filename)) "python_major": ctx.python_major
sys.exit(1) }
filename = xcodeproj[0] cookiecutter(template_dir, no_input=True, extra_context=context)
sh.open(filename) filename = join(
getcwd(),
"{}-ios".format(args.name.lower()),
"{}.xcodeproj".format(args.name.lower()),
"project.pbxproj")
update_pbxproj(filename, pbx_frameworks=args.add_framework)
print("--")
print("Project directory : {}-ios".format(
args.name.lower()))
print("XCode project : {0}-ios/{0}.xcodeproj".format(
args.name.lower()))
def _xcassets(self, title, command): def update(self):
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Generate {} for your project".format(title)) description="Update an existing xcode project")
parser.add_argument("filename", help="Path to your project or xcodeproj") parser.add_argument("filename", help="Path to your project or xcodeproj")
parser.add_argument("image", help="Path to your initial {}.png".format(title.lower())) parser.add_argument("--add-framework", action="append", help="Additional Frameworks to include with this project")
args = parser.parse_args(sys.argv[2:]) args = parser.parse_args(sys.argv[2:])
if not exists(args.image): filename = args.filename
logger.error("image path does not exists.") if not filename.endswith(".xcodeproj"):
return # try to find the xcodeproj
from glob import glob
xcodeproj = glob(join(filename, "*.xcodeproj"))
if not xcodeproj:
logger.error("Unable to find a xcodeproj in {}".format(filename))
sys.exit(1)
filename = xcodeproj[0]
filename = args.filename filename = join(filename, "project.pbxproj")
if not filename.endswith(".xcodeproj"): if not exists(filename):
# try to find the xcodeproj logger.error("{} not found".format(filename))
from glob import glob sys.exit(1)
xcodeproj = glob(join(filename, "*.xcodeproj"))
if not xcodeproj:
logger.error("Unable to find a xcodeproj in {}".format(filename))
sys.exit(1)
filename = xcodeproj[0]
project_name = filename.split("/")[-1].replace(".xcodeproj", "") update_pbxproj(filename, pbx_frameworks=args.add_framework)
images_xcassets = realpath(join(filename, "..", project_name, print("--")
"Images.xcassets")) print("Project {} updated".format(filename))
if not exists(images_xcassets):
logger.warning("Images.xcassets not found, creating it.")
makedirs(images_xcassets)
logger.info("Images.xcassets located at {}".format(images_xcassets))
command(images_xcassets, args.image) def build_info(self):
ctx = Context()
print("Build Context")
print("-------------")
for attr in dir(ctx):
if not attr.startswith("_"):
if not callable(attr) and attr != 'archs':
print("{}: {}".format(attr, pformat(getattr(ctx, attr))))
for arch in ctx.archs:
ul = '-' * (len(str(arch))+6)
print("\narch: {}\n{}".format(str(arch), ul))
for attr in dir(arch):
if not attr.startswith("_"):
if not callable(attr) and attr not in ['arch', 'ctx', 'get_env']:
print("{}: {}".format(attr, pformat(getattr(arch, attr))))
env = arch.get_env()
print("env ({}): {}".format(arch, pformat(env)))
def pip3(self):
self.pip()
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)
if not hasattr(ctx, "site_packages_dir"):
logger.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
}
pip_path = join(ctx.dist_dir, 'hostpython3', 'bin', 'pip3')
pip_args = []
if len(sys.argv) > 2 and sys.argv[2] == "install":
pip_args = ["--isolated", "--ignore-installed", "--prefix", ctx.python_prefix]
args = [pip_path] + [sys.argv[2]] + pip_args + sys.argv[3:]
else:
args = [pip_path] + pip_args + sys.argv[2:]
import os
logger.error("Executing pip with: {}".format(args))
os.execve(pip_path, args, pip_env)
def launchimage(self):
import xcassets
self._xcassets("LaunchImage", xcassets.launchimage)
def icon(self):
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:
logger.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))
parser.add_argument("filename", help="Path to your project or xcodeproj")
parser.add_argument("image", help="Path to your initial {}.png".format(title.lower()))
args = parser.parse_args(sys.argv[2:])
if not exists(args.image):
logger.error("image path does not exists.")
return
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:
logger.error("Unable to find a xcodeproj in {}".format(filename))
sys.exit(1)
filename = xcodeproj[0]
project_name = filename.split("/")[-1].replace(".xcodeproj", "")
images_xcassets = realpath(join(filename, "..", project_name,
"Images.xcassets"))
if not exists(images_xcassets):
logger.warning("Images.xcassets not found, creating it.")
makedirs(images_xcassets)
logger.info("Images.xcassets located at {}".format(images_xcassets))
command(images_xcassets, args.image)
def main():
ToolchainCL() ToolchainCL()