def apply_patches(self, arch, build_dir=None): '''Apply any patches for the Recipe. .. versionchanged:: 0.6.0 Add ability to apply patches from any dir via kwarg `build_dir`''' if self.patches: info_main('Applying patches for {}[{}]' .format(self.name, arch.arch)) if self.is_patched(arch): info_main('{} already patched, skipping'.format(self.name)) return build_dir = build_dir if build_dir else self.get_build_dir(arch.arch) for patch in self.patches: if isinstance(patch, (tuple, list)): patch, patch_check = patch if not patch_check(arch=arch, recipe=self): continue self.apply_patch( patch.format(version=self.version, arch=arch.arch), arch.arch, build_dir=build_dir) shprint(sh.touch, join(build_dir, '.patched'))
def apk(self, args): '''Create an APK using the given distribution.''' # AND: Need to add a parser here for any extra options # parser = argparse.ArgumentParser( # description='Build an APK') # args = parser.parse_args(args) ctx = self.ctx dist = self._dist # Manually fixing these arguments at the string stage is # unsatisfactory and should probably be changed somehow, but # we can't leave it until later as the build.py scripts assume # they are in the current directory. for i, arg in enumerate(args[:-1]): if arg in ('--dir', '--private'): args[i+1] = realpath(expanduser(args[i+1])) build = imp.load_source('build', join(dist.dist_dir, 'build.py')) with current_directory(dist.dist_dir): build.parse_args(args) shprint(sh.ant, 'debug', _tail=20, _critical=True) # AND: This is very crude, needs improving. Also only works # for debug for now. info_main('# Copying APK to current directory') apks = glob.glob(join(dist.dist_dir, 'bin', '*-*-debug.apk')) if len(apks) == 0: raise ValueError('Couldn\'t find the built APK') if len(apks) > 1: info('More than one built APK found...guessing you ' 'just built {}'.format(apks[-1])) shprint(sh.cp, apks[-1], './')
def download_if_necessary(self): info_main("Downloading {}".format(self.name)) user_dir = environ.get("P4A_{}_DIR".format(self.name.lower())) if user_dir is not None: info("P4A_{}_DIR is set, skipping download for {}".format(self.name, self.name)) return self.download()
def build_dist_from_args(ctx, dist, args): '''Parses out any bootstrap related arguments, and uses them to build a dist.''' bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) ctx.recipe_build_order = build_order ctx.python_modules = python_modules if python_modules and hasattr(sys, 'real_prefix'): error('virtualenv is needed to install pure-Python modules, but') error('virtualenv does not support nesting, and you are running') error('python-for-android in one. Please run p4a outside of a') error('virtualenv instead.') exit(1) info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}' .format(join(ctx.dist_dir, ctx.dist_name)))
def build_dist_from_args(ctx, dist, args): '''Parses out any bootstrap related arguments, and uses them to build a dist.''' bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) ctx.recipe_build_order = build_order info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}' .format(join(ctx.dist_dir, ctx.dist_name)))
def unpack(self, arch): info_main('Unpacking {} for {}'.format(self.name, arch)) build_dir = self.get_build_container_dir(arch) user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower())) if user_dir is not None: info("Installing KivyMD development versoion (from source)") self.clean_build() shprint(sh.rm, '-rf', build_dir) shprint(sh.mkdir, '-p', build_dir) shprint(sh.rmdir, build_dir) ensure_dir(build_dir) ensure_dir(build_dir + "/kivymd") shprint(sh.cp, user_dir + '/setup.py', self.get_build_dir(arch) + "/setup.py") shprint(sh.cp, '-a', user_dir + "/kivymd", self.get_build_dir(arch) + "/kivymd") return
def apply_patches(self, arch): """Apply any patches for the Recipe.""" if self.patches: info_main("Applying patches for {}[{}]".format(self.name, arch.arch)) if self.is_patched(arch): info_main("{} already patched, skipping".format(self.name)) return for patch in self.patches: if isinstance(patch, (tuple, list)): patch, patch_check = patch if not patch_check(arch=arch, recipe=self): continue self.apply_patch(patch.format(version=self.version, arch=arch.arch), arch.arch) shprint(sh.touch, join(self.get_build_dir(arch.arch), ".patched"))
def build_dist_from_args(ctx, dist, args_list): '''Parses out any bootstrap related arguments, and uses them to build a dist.''' parser = argparse.ArgumentParser( description='Create a newAndroid project') parser.add_argument( '--bootstrap', help=('The name of the bootstrap type, \'pygame\' ' 'or \'sdl2\', or leave empty to let a ' 'bootstrap be chosen automatically from your ' 'requirements.'), default=None) args, unknown = parser.parse_known_args(args_list) bs = Bootstrap.get_bootstrap(args.bootstrap, ctx) build_order, python_modules, bs \ = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs) info('The selected bootstrap is {}'.format(bs.name)) info_main('# Creating dist with {} bootstrap'.format(bs.name)) bs.distribution = dist info_notify('Dist will have name {} and recipes ({})'.format( dist.name, ', '.join(dist.recipes))) ctx.dist_name = bs.distribution.name ctx.prepare_bootstrap(bs) ctx.prepare_dist(ctx.dist_name) build_recipes(build_order, python_modules, ctx) ctx.bootstrap.run_distribute() info_main('# Your distribution was created successfully, exiting.') info('Dist can be found at (for now) {}' .format(join(ctx.dist_dir, ctx.dist_name))) return unknown
def unpack(self, arch): info_main('Unpacking {} for {}'.format(self.name, arch)) build_dir = self.get_build_container_dir(arch) user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower())) if user_dir is not None: info('P4A_{}_DIR exists, symlinking instead'.format( self.name.lower())) # AND: Currently there's something wrong if I use ln, fix this warning('Using cp -a instead of symlink...fix this!') if exists(self.get_build_dir(arch)): return shprint(sh.rm, '-rf', build_dir) shprint(sh.mkdir, '-p', build_dir) shprint(sh.rmdir, build_dir) ensure_dir(build_dir) shprint(sh.cp, '-a', user_dir, self.get_build_dir(arch)) return if self.url is None: info('Skipping {} unpack as no URL is set'.format(self.name)) return filename = shprint(sh.basename, self.versioned_url).stdout[:-1].decode('utf-8') with current_directory(build_dir): directory_name = self.get_build_dir(arch) # AND: Could use tito's get_archive_rootdir here if not exists(directory_name) or not isdir(directory_name): extraction_filename = join(self.ctx.packages_path, self.name, filename) if isfile(extraction_filename): if extraction_filename.endswith('.zip'): try: sh.unzip(extraction_filename) except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2): pass # return code 1 means unzipping had # warnings but did complete, # apparently happens sometimes with # github zips import zipfile fileh = zipfile.ZipFile(extraction_filename, 'r') root_directory = fileh.filelist[0].filename.split( '/')[0] if root_directory != basename(directory_name): shprint(sh.mv, root_directory, directory_name) elif (extraction_filename.endswith('.tar.gz') or extraction_filename.endswith('.tgz') or extraction_filename.endswith('.tar.bz2') or extraction_filename.endswith('.tbz2') or extraction_filename.endswith('.tar.xz') or extraction_filename.endswith('.txz')): sh.tar('xf', extraction_filename) root_directory = shprint( sh.tar, 'tf', extraction_filename).stdout.decode( 'utf-8').split('\n')[0].split('/')[0] if root_directory != directory_name: shprint(sh.mv, root_directory, directory_name) else: raise Exception( 'Could not extract {} download, it must be .zip, ' '.tar.gz or .tar.bz2 or .tar.xz'.format( extraction_filename)) elif isdir(extraction_filename): mkdir(directory_name) for entry in listdir(extraction_filename): if entry not in ('.git', ): shprint(sh.cp, '-Rv', join(extraction_filename, entry), directory_name) else: raise Exception( 'Given path is neither a file nor a directory: {}'. format(extraction_filename)) else: info('{} is already unpacked, skipping'.format(self.name))
def build_recipes(build_order, python_modules, ctx): # Put recipes in correct build order bs = ctx.bootstrap info_notify("Recipe build order is {}".format(build_order)) if python_modules: info_notify( ('The requirements ({}) were not found as recipes, they will be ' 'installed with pip.').format(', '.join(python_modules))) recipes = [Recipe.get_recipe(name, ctx) for name in build_order] # download is arch independent info_main('# Downloading recipes ') for recipe in recipes: recipe.download_if_necessary() for arch in ctx.archs: info_main('# Building all recipes for arch {}'.format(arch.arch)) info_main('# Unpacking recipes') for recipe in recipes: ensure_dir(recipe.get_build_container_dir(arch.arch)) recipe.prepare_build_dir(arch.arch) info_main('# Prebuilding recipes') # 2) prebuild packages for recipe in recipes: info_main('Prebuilding {} for {}'.format(recipe.name, arch.arch)) recipe.prebuild_arch(arch) recipe.apply_patches(arch) # 3) build packages info_main('# Building recipes') for recipe in recipes: info_main('Building {} for {}'.format(recipe.name, arch.arch)) if recipe.should_build(arch): recipe.build_arch(arch) else: info('{} said it is already built, skipping' .format(recipe.name)) # 4) biglink everything # AND: Should make this optional info_main('# Biglinking object files') if not ctx.python_recipe or not ctx.python_recipe.from_crystax: biglink(ctx, arch) else: info('NDK is crystax, skipping biglink (will this work?)') # 5) postbuild packages info_main('# Postbuilding recipes') for recipe in recipes: info_main('Postbuilding {} for {}'.format(recipe.name, arch.arch)) recipe.postbuild_arch(arch) info_main('# Installing pure Python modules') run_pymodules_install(ctx, python_modules) return
def apk(self, args): '''Create an APK using the given distribution.''' ctx = self.ctx dist = self._dist # Manually fixing these arguments at the string stage is # unsatisfactory and should probably be changed somehow, but # we can't leave it until later as the build.py scripts assume # they are in the current directory. fix_args = ('--dir', '--private', '--add-jar', '--add-source', '--whitelist', '--blacklist', '--presplash', '--icon') unknown_args = args.unknown_args for i, arg in enumerate(unknown_args[:-1]): argx = arg.split('=') if argx[0] in fix_args: if len(argx) > 1: unknown_args[i] = '='.join( (argx[0], realpath(expanduser(argx[1])))) else: unknown_args[i + 1] = realpath( expanduser(unknown_args[i + 1])) env = os.environ.copy() if args.build_mode == 'release': if args.keystore: env['P4A_RELEASE_KEYSTORE'] = realpath( expanduser(args.keystore)) if args.signkey: env['P4A_RELEASE_KEYALIAS'] = args.signkey if args.keystorepw: env['P4A_RELEASE_KEYSTORE_PASSWD'] = args.keystorepw if args.signkeypw: env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.signkeypw elif args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env: env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.keystorepw build = imp.load_source('build', join(dist.dist_dir, 'build.py')) with current_directory(dist.dist_dir): self.hook("before_apk_build") build_args = build.parse_args(args.unknown_args) self.hook("after_apk_build") self.hook("before_apk_assemble") try: ant = sh.Command('ant') except sh.CommandNotFound: error('Could not find ant binary, please install it and make ' 'sure it is in your $PATH.') exit(1) output = shprint(ant, args.build_mode, _tail=20, _critical=True, _env=env) self.hook("after_apk_assemble") info_main('# Copying APK to current directory') apk_re = re.compile(r'.*Package: (.*\.apk)$') apk_file = None for line in reversed(output.splitlines()): m = apk_re.match(line) if m: apk_file = m.groups()[0] break if not apk_file: info_main( '# APK filename not found in build output, trying to guess') suffix = args.build_mode if suffix == 'release' and not args.keystore: suffix = suffix + '-unsigned' apks = glob.glob( join(dist.dist_dir, 'bin', '*-*-{}.apk'.format(suffix))) if len(apks) == 0: raise ValueError('Couldn\'t find the built APK') if len(apks) > 1: info('More than one built APK found...guessing you ' 'just built {}'.format(apks[-1])) apk_file = apks[-1] info_main('# Found APK file: {}'.format(apk_file)) shprint(sh.cp, apk_file, './')
def apk(self, args): '''Create an APK using the given distribution.''' ap = argparse.ArgumentParser(description='Build an APK') ap.add_argument('--release', dest='build_mode', action='store_const', const='release', default='debug', help='Build the APK in Release mode') ap.add_argument( '--keystore', dest='keystore', action='store', default=None, help=('Keystore for JAR signing key, will use jarsigner ' 'default if not specified (release build only)')) ap.add_argument('--signkey', dest='signkey', action='store', default=None, help='Key alias to sign APK with (release build only)') ap.add_argument('--keystorepw', dest='keystorepw', action='store', default=None, help='Password for keystore') ap.add_argument('--signkeypw', dest='signkeypw', action='store', default=None, help='Password for key alias') apk_args, args = ap.parse_known_args(args) ctx = self.ctx dist = self._dist # Manually fixing these arguments at the string stage is # unsatisfactory and should probably be changed somehow, but # we can't leave it until later as the build.py scripts assume # they are in the current directory. fix_args = ('--dir', '--private', '--add-jar', '--add-source', '--whitelist', '--blacklist', '--presplash', '--icon') for i, arg in enumerate(args[:-1]): argx = arg.split('=') if argx[0] in fix_args: if len(argx) > 1: args[i] = '='.join( (argx[0], realpath(expanduser(argx[1])))) else: args[i + 1] = realpath(expanduser(args[i + 1])) env = os.environ.copy() if apk_args.build_mode == 'release': if apk_args.keystore: env['P4A_RELEASE_KEYSTORE'] = realpath( expanduser(apk_args.keystore)) if apk_args.signkey: env['P4A_RELEASE_KEYALIAS'] = apk_args.signkey if apk_args.keystorepw: env['P4A_RELEASE_KEYSTORE_PASSWD'] = apk_args.keystorepw if apk_args.signkeypw: env['P4A_RELEASE_KEYALIAS_PASSWD'] = apk_args.signkeypw elif apk_args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env: env['P4A_RELEASE_KEYALIAS_PASSWD'] = apk_args.keystorepw build = imp.load_source('build', join(dist.dist_dir, 'build.py')) with current_directory(dist.dist_dir): build_args = build.parse_args(args) output = shprint(sh.ant, apk_args.build_mode, _tail=20, _critical=True, _env=env) info_main('# Copying APK to current directory') apk_re = re.compile(r'.*Package: (.*\.apk)$') apk_file = None for line in reversed(output.splitlines()): m = apk_re.match(line) if m: apk_file = m.groups()[0] break if not apk_file: info_main( '# APK filename not found in build output, trying to guess') apks = glob.glob( join(dist.dist_dir, 'bin', '*-*-{}.apk'.format(apk_args.build_mode))) if len(apks) == 0: raise ValueError('Couldn\'t find the built APK') if len(apks) > 1: info('More than one built APK found...guessing you ' 'just built {}'.format(apks[-1])) apk_file = apks[-1] info_main('# Found APK file: {}'.format(apk_file)) shprint(sh.cp, apk_file, './')
def apk(self, args): '''Create an APK using the given distribution.''' ctx = self.ctx dist = self._dist # Manually fixing these arguments at the string stage is # unsatisfactory and should probably be changed somehow, but # we can't leave it until later as the build.py scripts assume # they are in the current directory. fix_args = ('--dir', '--private', '--add-jar', '--add-source', '--whitelist', '--blacklist', '--presplash', '--icon') unknown_args = args.unknown_args for i, arg in enumerate(unknown_args[:-1]): argx = arg.split('=') if argx[0] in fix_args: if len(argx) > 1: unknown_args[i] = '='.join((argx[0], realpath(expanduser(argx[1])))) else: unknown_args[i+1] = realpath(expanduser(unknown_args[i+1])) env = os.environ.copy() if args.build_mode == 'release': if args.keystore: env['P4A_RELEASE_KEYSTORE'] = realpath(expanduser(args.keystore)) if args.signkey: env['P4A_RELEASE_KEYALIAS'] = args.signkey if args.keystorepw: env['P4A_RELEASE_KEYSTORE_PASSWD'] = args.keystorepw if args.signkeypw: env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.signkeypw elif args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env: env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.keystorepw build = imp.load_source('build', join(dist.dist_dir, 'build.py')) with current_directory(dist.dist_dir): self.hook("before_apk_build") os.environ["ANDROID_API"] = str(self.ctx.android_api) build_args = build.parse_args(args.unknown_args) self.hook("after_apk_build") self.hook("before_apk_assemble") build_type = ctx.java_build_tool if build_type == 'auto': info('Selecting java build tool:') build_tools_versions = os.listdir(join(ctx.sdk_dir, 'build-tools')) build_tools_versions = sorted(build_tools_versions, key=LooseVersion) build_tools_version = build_tools_versions[-1] info(('Detected highest available build tools ' 'version to be {}').format(build_tools_version)) if build_tools_version >= '25.0' and exists('gradlew'): build_type = 'gradle' info(' Building with gradle, as gradle executable is present') else: build_type = 'ant' if build_tools_version < '25.0': info((' Building with ant, as the highest ' 'build-tools-version is only {}').format(build_tools_version)) else: info(' Building with ant, as no gradle executable detected') if build_type == 'gradle': # gradle-based build env["ANDROID_NDK_HOME"] = self.ctx.ndk_dir env["ANDROID_HOME"] = self.ctx.sdk_dir gradlew = sh.Command('./gradlew') if args.build_mode == "debug": gradle_task = "assembleDebug" elif args.build_mode == "release": gradle_task = "assembleRelease" else: error("Unknown build mode {} for apk()".format( args.build_mode)) exit(1) output = shprint(gradlew, gradle_task, _tail=20, _critical=True, _env=env) # gradle output apks somewhere else # and don't have version in file apk_dir = join(dist.dist_dir, "build", "outputs", "apk") apk_glob = "*-{}.apk" apk_add_version = True else: # ant-based build try: ant = sh.Command('ant') except sh.CommandNotFound: error('Could not find ant binary, please install it ' 'and make sure it is in your $PATH.') exit(1) output = shprint(ant, args.build_mode, _tail=20, _critical=True, _env=env) apk_dir = join(dist.dist_dir, "bin") apk_glob = "*-*-{}.apk" apk_add_version = False self.hook("after_apk_assemble") info_main('# Copying APK to current directory') apk_re = re.compile(r'.*Package: (.*\.apk)$') apk_file = None for line in reversed(output.splitlines()): m = apk_re.match(line) if m: apk_file = m.groups()[0] break if not apk_file: info_main('# APK filename not found in build output, trying to guess') if args.build_mode == "release": suffixes = ("release", "release-unsigned") else: suffixes = ("debug", ) for suffix in suffixes: apks = glob.glob(join(apk_dir, apk_glob.format(suffix))) if apks: if len(apks) > 1: info('More than one built APK found... guessing you ' 'just built {}'.format(apks[-1])) apk_file = apks[-1] break else: raise ValueError('Couldn\'t find the built APK') info_main('# Found APK file: {}'.format(apk_file)) if apk_add_version: info('# Add version number to APK') apk_name, apk_suffix = basename(apk_file).split("-", 1) apk_file_dest = "{}-{}-{}".format( apk_name, build_args.version, apk_suffix) info('# APK renamed to {}'.format(apk_file_dest)) shprint(sh.cp, apk_file, apk_file_dest) else: shprint(sh.cp, apk_file, './')
def unpack(self, arch): info_main('Unpacking {} for {}'.format(self.name, arch)) build_dir = self.get_build_container_dir(arch) user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower())) if user_dir is not None: info('P4A_{}_DIR exists, symlinking instead'.format( self.name.lower())) if exists(self.get_build_dir(arch)): return shprint(sh.rm, '-rf', build_dir) shprint(sh.mkdir, '-p', build_dir) shprint(sh.rmdir, build_dir) ensure_dir(build_dir) shprint(sh.cp, '-a', user_dir, self.get_build_dir(arch)) return if self.url is None: info('Skipping {} unpack as no URL is set'.format(self.name)) return filename = shprint( sh.basename, self.versioned_url).stdout[:-1].decode('utf-8') ma = match(u'^(.+)#md5=([0-9a-f]{32})$', filename) if ma: # fragmented URL? filename = ma.group(1) with current_directory(build_dir): directory_name = self.get_build_dir(arch) if not exists(directory_name) or not isdir(directory_name): extraction_filename = join( self.ctx.packages_path, self.name, filename) if isfile(extraction_filename): if extraction_filename.endswith('.zip'): try: sh.unzip(extraction_filename) except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2): # return code 1 means unzipping had # warnings but did complete, # apparently happens sometimes with # github zips pass import zipfile fileh = zipfile.ZipFile(extraction_filename, 'r') root_directory = fileh.filelist[0].filename.split('/')[0] if root_directory != basename(directory_name): shprint(sh.mv, root_directory, directory_name) elif (extraction_filename.endswith('.tar.gz') or extraction_filename.endswith('.tgz') or extraction_filename.endswith('.tar.bz2') or extraction_filename.endswith('.tbz2') or extraction_filename.endswith('.tar.xz') or extraction_filename.endswith('.txz')): sh.tar('xf', extraction_filename) root_directory = shprint( sh.tar, 'tf', extraction_filename).stdout.decode( 'utf-8').split('\n')[0].split('/')[0] if root_directory != directory_name: shprint(sh.mv, root_directory, directory_name) else: raise Exception( 'Could not extract {} download, it must be .zip, ' '.tar.gz or .tar.bz2 or .tar.xz'.format(extraction_filename)) elif isdir(extraction_filename): mkdir(directory_name) for entry in listdir(extraction_filename): if entry not in ('.git',): shprint(sh.cp, '-Rv', join(extraction_filename, entry), directory_name) else: raise Exception( 'Given path is neither a file nor a directory: {}' .format(extraction_filename)) else: info('{} is already unpacked, skipping'.format(self.name))
def apk(self, args): '''Create an APK using the given distribution.''' ctx = self.ctx dist = self._dist # Manually fixing these arguments at the string stage is # unsatisfactory and should probably be changed somehow, but # we can't leave it until later as the build.py scripts assume # they are in the current directory. fix_args = ('--dir', '--private', '--add-jar', '--add-source', '--whitelist', '--blacklist', '--presplash', '--icon') unknown_args = args.unknown_args for i, arg in enumerate(unknown_args[:-1]): argx = arg.split('=') if argx[0] in fix_args: if len(argx) > 1: unknown_args[i] = '='.join((argx[0], realpath(expanduser(argx[1])))) else: unknown_args[i+1] = realpath(expanduser(unknown_args[i+1])) env = os.environ.copy() if args.build_mode == 'release': if args.keystore: env['P4A_RELEASE_KEYSTORE'] = realpath(expanduser(args.keystore)) if args.signkey: env['P4A_RELEASE_KEYALIAS'] = args.signkey if args.keystorepw: env['P4A_RELEASE_KEYSTORE_PASSWD'] = args.keystorepw if args.signkeypw: env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.signkeypw elif args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env: env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.keystorepw build = imp.load_source('build', join(dist.dist_dir, 'build.py')) with current_directory(dist.dist_dir): self.hook("before_apk_build") build_args = build.parse_args(args.unknown_args) self.hook("after_apk_build") self.hook("before_apk_assemble") try: ant = sh.Command('ant') except sh.CommandNotFound: error('Could not find ant binary, please install it and make ' 'sure it is in your $PATH.') exit(1) output = shprint(ant, args.build_mode, _tail=20, _critical=True, _env=env) self.hook("after_apk_assemble") info_main('# Copying APK to current directory') apk_re = re.compile(r'.*Package: (.*\.apk)$') apk_file = None for line in reversed(output.splitlines()): m = apk_re.match(line) if m: apk_file = m.groups()[0] break if not apk_file: info_main('# APK filename not found in build output, trying to guess') suffix = args.build_mode if suffix == 'release' and not args.keystore: suffix = suffix + '-unsigned' apks = glob.glob(join(dist.dist_dir, 'bin', '*-*-{}.apk'.format(suffix))) if len(apks) == 0: raise ValueError('Couldn\'t find the built APK') if len(apks) > 1: info('More than one built APK found...guessing you ' 'just built {}'.format(apks[-1])) apk_file = apks[-1] info_main('# Found APK file: {}'.format(apk_file)) shprint(sh.cp, apk_file, './')
def apk(self, args): """Create an APK using the given distribution.""" ctx = self.ctx dist = self._dist # Manually fixing these arguments at the string stage is # unsatisfactory and should probably be changed somehow, but # we can't leave it until later as the build.py scripts assume # they are in the current directory. fix_args = ('--dir', '--private', '--add-jar', '--add-source', '--whitelist', '--blacklist', '--presplash', '--icon') unknown_args = args.unknown_args for i, arg in enumerate(unknown_args): argx = arg.split('=') if argx[0] in fix_args: if len(argx) > 1: unknown_args[i] = '='.join( (argx[0], realpath(expanduser(argx[1])))) else: unknown_args[i + 1] = realpath( expanduser(unknown_args[i + 1])) env = os.environ.copy() if args.build_mode == 'release': if args.keystore: env['P4A_RELEASE_KEYSTORE'] = realpath( expanduser(args.keystore)) if args.signkey: env['P4A_RELEASE_KEYALIAS'] = args.signkey if args.keystorepw: env['P4A_RELEASE_KEYSTORE_PASSWD'] = args.keystorepw if args.signkeypw: env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.signkeypw elif args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env: env['P4A_RELEASE_KEYALIAS_PASSWD'] = args.keystorepw build = imp.load_source('build', join(dist.dist_dir, 'build.py')) with current_directory(dist.dist_dir): self.hook("before_apk_build") os.environ["ANDROID_API"] = str(self.ctx.android_api) build_args = build.parse_args(args.unknown_args) self.hook("after_apk_build") self.hook("before_apk_assemble") build_type = ctx.java_build_tool if build_type == 'auto': info('Selecting java build tool:') build_tools_versions = os.listdir( join(ctx.sdk_dir, 'build-tools')) build_tools_versions = sorted(build_tools_versions, key=LooseVersion) build_tools_version = build_tools_versions[-1] info(('Detected highest available build tools ' 'version to be {}').format(build_tools_version)) if build_tools_version >= '25.0' and exists('gradlew'): build_type = 'gradle' info(' Building with gradle, as gradle executable is ' 'present') else: build_type = 'ant' if build_tools_version < '25.0': info((' Building with ant, as the highest ' 'build-tools-version is only {}' ).format(build_tools_version)) else: info(' Building with ant, as no gradle executable ' 'detected') if build_type == 'gradle': # gradle-based build env["ANDROID_NDK_HOME"] = self.ctx.ndk_dir env["ANDROID_HOME"] = self.ctx.sdk_dir gradlew = sh.Command('./gradlew') if exists('/usr/bin/dos2unix'): # .../dists/bdisttest_python3/gradlew # .../build/bootstrap_builds/sdl2-python3crystax/gradlew # if docker on windows, gradle contains CRLF output = shprint(sh.Command('dos2unix'), gradlew._path.decode('utf8'), _tail=20, _critical=True, _env=env) if args.build_mode == "debug": gradle_task = "assembleDebug" elif args.build_mode == "release": gradle_task = "assembleRelease" else: raise BuildInterruptingException( "Unknown build mode {} for apk()".format( args.build_mode)) output = shprint(gradlew, gradle_task, _tail=20, _critical=True, _env=env) # gradle output apks somewhere else # and don't have version in file apk_dir = join(dist.dist_dir, "build", "outputs", "apk", args.build_mode) apk_glob = "*-{}.apk" apk_add_version = True else: # ant-based build try: ant = sh.Command('ant') except sh.CommandNotFound: raise BuildInterruptingException( 'Could not find ant binary, please install it ' 'and make sure it is in your $PATH.') output = shprint(ant, args.build_mode, _tail=20, _critical=True, _env=env) apk_dir = join(dist.dist_dir, "bin") apk_glob = "*-*-{}.apk" apk_add_version = False self.hook("after_apk_assemble") info_main('# Copying APK to current directory') apk_re = re.compile(r'.*Package: (.*\.apk)$') apk_file = None for line in reversed(output.splitlines()): m = apk_re.match(line) if m: apk_file = m.groups()[0] break if not apk_file: info_main('# APK filename not found in build output. Guessing...') if args.build_mode == "release": suffixes = ("release", "release-unsigned") else: suffixes = ("debug", ) for suffix in suffixes: apks = glob.glob(join(apk_dir, apk_glob.format(suffix))) if apks: if len(apks) > 1: info('More than one built APK found... guessing you ' 'just built {}'.format(apks[-1])) apk_file = apks[-1] break else: raise BuildInterruptingException( 'Couldn\'t find the built APK') info_main('# Found APK file: {}'.format(apk_file)) if apk_add_version: info('# Add version number to APK') apk_name, apk_suffix = basename(apk_file).split("-", 1) apk_file_dest = "{}-{}-{}".format(apk_name, build_args.version, apk_suffix) info('# APK renamed to {}'.format(apk_file_dest)) shprint(sh.cp, apk_file, apk_file_dest) else: shprint(sh.cp, apk_file, './')
def apk(self, args): '''Create an APK using the given distribution.''' ap = argparse.ArgumentParser( description='Build an APK') ap.add_argument('--release', dest='build_mode', action='store_const', const='release', default='debug', help='Build the APK in Release mode') ap.add_argument('--keystore', dest='keystore', action='store', default=None, help=('Keystore for JAR signing key, will use jarsigner ' 'default if not specified (release build only)')) ap.add_argument('--signkey', dest='signkey', action='store', default=None, help='Key alias to sign APK with (release build only)') ap.add_argument('--keystorepw', dest='keystorepw', action='store', default=None, help='Password for keystore') ap.add_argument('--signkeypw', dest='signkeypw', action='store', default=None, help='Password for key alias') apk_args, args = ap.parse_known_args(args) ctx = self.ctx dist = self._dist # Manually fixing these arguments at the string stage is # unsatisfactory and should probably be changed somehow, but # we can't leave it until later as the build.py scripts assume # they are in the current directory. fix_args = ('--dir', '--private', '--add-jar', '--add-source', '--whitelist', '--blacklist', '--presplash', '--icon') for i, arg in enumerate(args[:-1]): argx = arg.split('=') if argx[0] in fix_args: if len(argx) > 1: args[i] = '='.join((argx[0], realpath(expanduser(argx[1])))) else: args[i+1] = realpath(expanduser(args[i+1])) env = os.environ.copy() if apk_args.build_mode == 'release': if apk_args.keystore: env['P4A_RELEASE_KEYSTORE'] = realpath(expanduser(apk_args.keystore)) if apk_args.signkey: env['P4A_RELEASE_KEYALIAS'] = apk_args.signkey if apk_args.keystorepw: env['P4A_RELEASE_KEYSTORE_PASSWD'] = apk_args.keystorepw if apk_args.signkeypw: env['P4A_RELEASE_KEYALIAS_PASSWD'] = apk_args.signkeypw elif apk_args.keystorepw and 'P4A_RELEASE_KEYALIAS_PASSWD' not in env: env['P4A_RELEASE_KEYALIAS_PASSWD'] = apk_args.keystorepw build = imp.load_source('build', join(dist.dist_dir, 'build.py')) with current_directory(dist.dist_dir): build_args = build.parse_args(args) output = shprint(sh.ant, apk_args.build_mode, _tail=20, _critical=True, _env=env) info_main('# Copying APK to current directory') apk_re = re.compile(r'.*Package: (.*\.apk)$') apk_file = None for line in reversed(output.splitlines()): m = apk_re.match(line) if m: apk_file = m.groups()[0] break if not apk_file: info_main('# APK filename not found in build output, trying to guess') apks = glob.glob(join(dist.dist_dir, 'bin', '*-*-{}.apk'.format(apk_args.build_mode))) if len(apks) == 0: raise ValueError('Couldn\'t find the built APK') if len(apks) > 1: info('More than one built APK found...guessing you ' 'just built {}'.format(apks[-1])) apk_file = apks[-1] info_main('# Found APK file: {}'.format(apk_file)) shprint(sh.cp, apk_file, './')
def unpack(self, arch): info_main("Unpacking {} for {}".format(self.name, arch)) build_dir = self.get_build_container_dir(arch) user_dir = environ.get("P4A_{}_DIR".format(self.name.lower())) if user_dir is not None: info("P4A_{}_DIR exists, symlinking instead".format(self.name.lower())) # AND: Currently there's something wrong if I use ln, fix this warning("Using cp -a instead of symlink...fix this!") if exists(self.get_build_dir(arch)): return shprint(sh.rm, "-rf", build_dir) shprint(sh.mkdir, "-p", build_dir) shprint(sh.rmdir, build_dir) ensure_dir(build_dir) shprint(sh.cp, "-a", user_dir, self.get_build_dir(arch)) return if self.url is None: info("Skipping {} unpack as no URL is set".format(self.name)) return filename = shprint(sh.basename, self.versioned_url).stdout[:-1].decode("utf-8") with current_directory(build_dir): directory_name = self.get_build_dir(arch) # AND: Could use tito's get_archive_rootdir here if not exists(directory_name) or not isdir(directory_name): extraction_filename = join(self.ctx.packages_path, self.name, filename) if isfile(extraction_filename): if extraction_filename.endswith(".zip"): try: sh.unzip(extraction_filename) except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2): pass # return code 1 means unzipping had # warnings but did complete, # apparently happens sometimes with # github zips import zipfile fileh = zipfile.ZipFile(extraction_filename, "r") root_directory = fileh.filelist[0].filename.split("/")[0] if root_directory != basename(directory_name): shprint(sh.mv, root_directory, directory_name) elif ( extraction_filename.endswith(".tar.gz") or extraction_filename.endswith(".tgz") or extraction_filename.endswith(".tar.bz2") or extraction_filename.endswith(".tbz2") or extraction_filename.endswith(".tar.xz") or extraction_filename.endswith(".txz") ): sh.tar("xf", extraction_filename) root_directory = ( shprint(sh.tar, "tf", extraction_filename) .stdout.decode("utf-8") .split("\n")[0] .split("/")[0] ) if root_directory != directory_name: shprint(sh.mv, root_directory, directory_name) else: raise Exception( "Could not extract {} download, it must be .zip, " ".tar.gz or .tar.bz2 or .tar.xz" ) elif isdir(extraction_filename): mkdir(directory_name) for entry in listdir(extraction_filename): if entry not in (".git",): shprint(sh.cp, "-Rv", join(extraction_filename, entry), directory_name) else: raise Exception("Given path is neither a file nor a directory: {}".format(extraction_filename)) else: info("{} is already unpacked, skipping".format(self.name))
def build_recipes(build_order, python_modules, ctx, project_dir, ignore_project_setup_py=False): # Put recipes in correct build order info_notify("Recipe build order is {}".format(build_order)) if python_modules: python_modules = sorted(set(python_modules)) info_notify( ('The requirements ({}) were not found as recipes, they will be ' 'installed with pip.').format(', '.join(python_modules))) recipes = [Recipe.get_recipe(name, ctx) for name in build_order] # download is arch independent info_main('# Downloading recipes ') for recipe in recipes: recipe.download_if_necessary() for arch in ctx.archs: info_main('# Building all recipes for arch {}'.format(arch.arch)) info_main('# Unpacking recipes') for recipe in recipes: ensure_dir(recipe.get_build_container_dir(arch.arch)) recipe.prepare_build_dir(arch.arch) info_main('# Prebuilding recipes') # 2) prebuild packages for recipe in recipes: info_main('Prebuilding {} for {}'.format(recipe.name, arch.arch)) recipe.prebuild_arch(arch) recipe.apply_patches(arch) # 3) build packages info_main('# Building recipes') for recipe in recipes: info_main('Building {} for {}'.format(recipe.name, arch.arch)) if recipe.should_build(arch): recipe.build_arch(arch) recipe.install_libraries(arch) else: info('{} said it is already built, skipping'.format( recipe.name)) # 4) biglink everything info_main('# Biglinking object files') if not ctx.python_recipe: biglink(ctx, arch) else: warning("Context's python recipe found, " "skipping biglink (will this work?)") # 5) postbuild packages info_main('# Postbuilding recipes') for recipe in recipes: info_main('Postbuilding {} for {}'.format(recipe.name, arch.arch)) recipe.postbuild_arch(arch) info_main('# Installing pure Python modules') run_pymodules_install(ctx, python_modules, project_dir, ignore_setup_py=ignore_project_setup_py) return
def unpack(self, arch): info_main('Unpacking {} for {}'.format(self.name, arch)) build_dir = self.get_build_container_dir(arch) user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower())) if user_dir is not None: info('P4A_{}_DIR exists, symlinking instead'.format( self.name.lower())) # AND: Currently there's something wrong if I use ln, fix this warning('Using git clone instead of symlink...fix this!') if exists(self.get_build_dir(arch)): return shprint(sh.rm, '-rf', build_dir) shprint(sh.mkdir, '-p', build_dir) shprint(sh.rmdir, build_dir) ensure_dir(build_dir) shprint(sh.git, 'clone', user_dir, self.get_build_dir(arch)) return if self.url is None: info('Skipping {} unpack as no URL is set'.format(self.name)) return filename = shprint( sh.basename, self.versioned_url).stdout[:-1].decode('utf-8') with current_directory(build_dir): directory_name = self.get_build_dir(arch) # AND: Could use tito's get_archive_rootdir here if not exists(directory_name) or not isdir(directory_name): extraction_filename = join( self.ctx.packages_path, self.name, filename) if isfile(extraction_filename): if extraction_filename.endswith('.tar.gz') or \ extraction_filename.endswith('.tgz'): sh.tar('xzf', extraction_filename) root_directory = shprint( sh.tar, 'tzf', extraction_filename).stdout.decode( 'utf-8').split('\n')[0].split('/')[0] if root_directory != directory_name: shprint(sh.mv, root_directory, directory_name) elif (extraction_filename.endswith('.tar.bz2') or extraction_filename.endswith('.tbz2')): info('Extracting {} at {}' .format(extraction_filename, filename)) sh.tar('xjf', extraction_filename) root_directory = sh.tar( 'tjf', extraction_filename).stdout.decode( 'utf-8').split('\n')[0].split('/')[0] if root_directory != directory_name: shprint(sh.mv, root_directory, directory_name) elif extraction_filename.endswith('.zip'): sh.unzip(extraction_filename) import zipfile fileh = zipfile.ZipFile(extraction_filename, 'r') root_directory = fileh.filelist[0].filename.strip('/') if root_directory != directory_name: shprint(sh.mv, root_directory, directory_name) else: raise Exception( 'Could not extract {} download, it must be .zip, ' '.tar.gz or .tar.bz2') elif isdir(extraction_filename): mkdir(directory_name) for entry in listdir(extraction_filename): if entry not in ('.git',): shprint(sh.cp, '-Rv', join(extraction_filename, entry), directory_name) else: raise Exception( 'Given path is neither a file nor a directory: {}' .format(extraction_filename)) else: info('{} is already unpacked, skipping'.format(self.name))
def build_recipes(build_order, python_modules, ctx): # Put recipes in correct build order bs = ctx.bootstrap info_notify("Recipe build order is {}".format(build_order)) if python_modules: python_modules = sorted(set(python_modules)) info_notify( ('The requirements ({}) were not found as recipes, they will be ' 'installed with pip.').format(', '.join(python_modules))) recipes = [Recipe.get_recipe(name, ctx) for name in build_order] # download is arch independent info_main('# Downloading recipes ') for recipe in recipes: if ctx.P4A_force_build and not recipe.force_build: info_main( 'recipe {0} not marked for force build, skip download'.format( recipe.name)) continue recipe.download_if_necessary() for arch in ctx.archs: info_main('# Building all recipes for arch {}'.format(arch.arch)) info_main('# Unpacking recipes') for recipe in recipes: if ctx.P4A_force_build and not recipe.force_build: info_main('recipe {0} not marked for force build, skip unpack'. format(recipe.name)) continue ensure_dir(recipe.get_build_container_dir(arch.arch)) recipe.prepare_build_dir(arch.arch) info_main('# Prebuilding recipes') # 2) prebuild packages for recipe in recipes: info_main('Prebuilding {} for {}'.format(recipe.name, arch.arch)) recipe.prebuild_arch(arch) recipe.apply_patches(arch) # 3) build packages info_main('# Building recipes') for recipe in recipes: info_main('Building {} for {}'.format(recipe.name, arch.arch)) if recipe.force_build or recipe.should_build(arch): recipe.build_arch(arch) else: info('{} said it is already built, skipping'.format( recipe.name)) # 4) biglink everything # AND: Should make this optional info_main('# Biglinking object files') if not ctx.python_recipe or not ctx.python_recipe.from_crystax: biglink(ctx, arch) else: info('NDK is crystax, skipping biglink (will this work?)') # 5) postbuild packages info_main('# Postbuilding recipes') for recipe in recipes: info_main('Postbuilding {} for {}'.format(recipe.name, arch.arch)) recipe.postbuild_arch(arch) info_main('# Installing pure Python modules') run_pymodules_install(ctx, python_modules) return