Esempio n. 1
0
 def install_libs(self, arch, *libs):
     libs_dir = self.ctx.get_libs_dir(arch.arch)
     if not libs:
         warning('install_libs called with no libraries to install!')
         return
     args = libs + (libs_dir,)
     shprint(sh.cp, *args)
Esempio n. 2
0
    def get_distributions(cls, ctx, extra_dist_dirs=[]):
        '''Returns all the distributions found locally.'''
        if extra_dist_dirs:
            warning('extra_dist_dirs argument to get_distributions '
                    'is not yet implemented')
            exit(1)
        dist_dir = ctx.dist_dir
        folders = glob.glob(join(dist_dir, '*'))
        for dir in extra_dist_dirs:
            folders.extend(glob.glob(join(dir, '*')))

        dists = []
        for folder in folders:
            if exists(join(folder, 'dist_info.json')):
                with open(join(folder, 'dist_info.json')) as fileh:
                    dist_info = json.load(fileh)
                dist = cls(ctx)
                dist.name = folder.split('/')[-1]
                dist.dist_dir = folder
                dist.needs_build = False
                dist.recipes = dist_info['recipes']
                if 'archs' in dist_info:
                    dist.archs = dist_info['archs']
                dists.append(dist)
        return dists
Esempio n. 3
0
    def strip_libraries(self, arch):
        info('Stripping libraries')
        if self.ctx.python_recipe.from_crystax:
            info('Python was loaded from CrystaX, skipping strip')
            return
        env = arch.get_env()
        strip = which('arm-linux-androideabi-strip', env['PATH'])
        if strip is None:
            warning('Can\'t find strip in PATH...')
            return
        strip = sh.Command(strip)

        libs_dir = join(self.dist_dir, '_python_bundle',
                        '_python_bundle', 'modules')
        if self.ctx.python_recipe.name == 'python2legacy':
            libs_dir = join(self.dist_dir, 'private')
        filens = shprint(sh.find, libs_dir, join(self.dist_dir, 'libs'),
                         '-iname', '*.so', _env=env).stdout.decode('utf-8')

        logger.info('Stripping libraries in private dir')
        for filen in filens.split('\n'):
            try:
                strip(filen, _env=env)
            except sh.ErrorReturnCode_1:
                logger.debug('Failed to strip ' + filen)
Esempio n. 4
0
    def build_arch(self, arch):
        env = self.get_recipe_env(arch)
        
        env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/png -I{jni_path}/jpeg'.format(
            jni_path=join(self.ctx.bootstrap.build_dir, 'jni'))
        env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/sdl/include -I{jni_path}/sdl_mixer'.format(
            jni_path=join(self.ctx.bootstrap.build_dir, 'jni'))
        env['CFLAGS'] = env['CFLAGS'] + ' -I{jni_path}/sdl_ttf -I{jni_path}/sdl_image'.format(
            jni_path=join(self.ctx.bootstrap.build_dir, 'jni'))
        debug('pygame cflags', env['CFLAGS'])

        
        env['LDFLAGS'] = env['LDFLAGS'] + ' -L{libs_path} -L{src_path}/obj/local/{arch} -lm -lz'.format(
            libs_path=self.ctx.libs_dir, src_path=self.ctx.bootstrap.build_dir, arch=env['ARCH'])

        env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')

        with current_directory(self.get_build_dir(arch.arch)):
            info('hostpython is ' + self.ctx.hostpython)
            hostpython = sh.Command(self.ctx.hostpython)
            shprint(hostpython, 'setup.py', 'install', '-O2', _env=env,
                    _tail=10, _critical=True)

            info('strip is ' + env['STRIP'])
            build_lib = glob.glob('./build/lib*')
            assert len(build_lib) == 1
            print('stripping pygame')
            shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
                    env['STRIP'], '{}', ';')

        python_install_path = join(self.ctx.build_dir, 'python-install')
        warning('Should remove pygame tests etc. here, but skipping for now')
Esempio n. 5
0
    def get_newest_toolchain(self, arch):

        # warning("get_newest_toolchain(self, arch), toolchain prefix = {}".format(toolchain_prefix))
        # [WARNING]: get_newest_toolchain(self, arch), toolchain prefix = arm-linux-androideabi

        toolchain_versions = []
        toolchain_prefix   = arch.toolchain_prefix
        toolchain_path     = join(self.ctx.ndk_dir, 'toolchains')
        if isdir(toolchain_path):
            toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
                                                            toolchain_prefix))
            toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
                                  for path in toolchain_contents]
        else:
            warning('Could not find toolchain subdirectory!')
        toolchain_versions.sort()

        toolchain_versions_gcc = []
        for toolchain_version in toolchain_versions:
            if toolchain_version[0].isdigit():
                toolchain_versions_gcc.append(toolchain_version) # GCC toolchains begin with a number

        if toolchain_versions:
            toolchain_version = toolchain_versions_gcc[-1] # the latest gcc toolchain
        else:
            warning('Could not find any toolchain for {}!'.format(toolchain_prefix))

        self.toolchain_version = toolchain_version
Esempio n. 6
0
    def clean_build(self, arch=None):
        """Deletes all the build information of the recipe.

        If arch is not None, only this arch dir is deleted. Otherwise
        (the default) all builds for all archs are deleted.

        By default, this just deletes the main build dir. If the
        recipe has e.g. object files biglinked, or .so files stored
        elsewhere, you should override this method.

        This method is intended for testing purposes, it may have
        strange results. Rebuild everything if this seems to happen.

        """
        if arch is None:
            base_dir = join(self.ctx.build_dir, "other_builds", self.name)
        else:
            base_dir = self.get_build_container_dir(arch)
        dirs = glob.glob(base_dir + "-*")
        if exists(base_dir):
            dirs.append(base_dir)
        if not dirs:
            warning(("Attempted to clean build for {} but found no existing " "build dirs").format(self.name))

        for directory in dirs:
            if exists(directory):
                info("Deleting {}".format(directory))
                shutil.rmtree(directory)
Esempio n. 7
0
 def recipes(self, args):
     ctx = self.ctx
     if args.compact:
         print(" ".join(set(Recipe.list_recipes(ctx))))
     else:
         for name in sorted(Recipe.list_recipes(ctx)):
             try:
                 recipe = Recipe.get_recipe(name, ctx)
             except IOError:
                 warning('Recipe "{}" could not be loaded'.format(name))
             except SyntaxError:
                 import traceback
                 traceback.print_exc()
                 warning(('Recipe "{}" could not be loaded due to a '
                          'syntax error').format(name))
             version = str(recipe.version)
             print('{Fore.BLUE}{Style.BRIGHT}{recipe.name:<12} '
                   '{Style.RESET_ALL}{Fore.LIGHTBLUE_EX}'
                   '{version:<8}{Style.RESET_ALL}'.format(
                     recipe=recipe, Fore=Out_Fore, Style=Out_Style,
                     version=version))
             print('    {Fore.GREEN}depends: {recipe.depends}'
                   '{Fore.RESET}'.format(recipe=recipe, Fore=Out_Fore))
             if recipe.conflicts:
                 print('    {Fore.RED}conflicts: {recipe.conflicts}'
                       '{Fore.RESET}'
                       .format(recipe=recipe, Fore=Out_Fore))
             if recipe.opt_depends:
                 print('    {Fore.YELLOW}optional depends: '
                       '{recipe.opt_depends}{Fore.RESET}'
                       .format(recipe=recipe, Fore=Out_Fore))
Esempio n. 8
0
    def prebuild_arch(self, arch):
        super(MobileInsightRecipe, self).prebuild_arch(arch)

        build_dir = self.get_build_dir(arch.arch)
        tmp_dir = join(build_dir, 'mi_tmp')
        info("Cleaning old MobileInsight-core sources at {}".format(build_dir))
        try:
            shprint(sh.rm, '-r',
                    build_dir,
                    _tail     = 20,
                    _critical = True)
        except:
            pass

        if LOCAL_DEBUG is False:
            info("Cloning MobileInsight-core sources from {}".format(self.mi_git))
            shprint(sh.git,
                    'clone', '-b',
                    self.mi_branch,
                    '--depth=1',
                    self.mi_git,
                    tmp_dir,
                    _tail     = 20,
                    _critical = True)
        else:
            warning("Debugging using local sources of MobileInsight at {}".format(self.local_src))
            shprint(sh.mkdir,
                    build_dir,
                    _tail     = 20,
                    _critical = True)
            shprint(sh.mkdir,
                    tmp_dir,
                    _tail     = 20,
                    _critical = True)
            shprint(sh.cp,
                    '-fr',
                    self.local_src,
                    tmp_dir,
                    _tail     = 20,
                    _critical = True)
            tmp_dir = join(tmp_dir, 'MobileInsight-core')

        shprint(sh.mv,
                join(tmp_dir, 'mobile_insight'),
                build_dir,
                _tail     = 20,
                _critical = True)

        shprint(sh.mv,
                join(tmp_dir, 'dm_collector_c'),
                build_dir,
                _tail     = 20,
                _critical = True)

        # remove unnecessary codes
        shprint(sh.rm, '-r', tmp_dir,
                _tail     = 20,
                _critical = True)

        self.get_newest_toolchain(arch)
Esempio n. 9
0
    def get_recipe_env(self, arch):
        env = super(MobileInsightRecipe, self).get_recipe_env(arch)

        warning("get_recipe_env(self, arch), use toolchain version = {toolchain_version}".format(
            toolchain_version   = self.toolchain_version))
        env['CFLAGS'] += ' -fPIC'
        env['CFLAGS'] += ' -I{ndk_dir}/sources/cxx-stl/gnu-libstdc++/{toolchain_version}/include'.format(
            ndk_dir             = self.ctx.ndk_dir,
            toolchain_version   = self.toolchain_version)
        env['CFLAGS'] += ' -I{ndk_dir}/sources/cxx-stl/gnu-libstdc++/{toolchain_version}/libs/{arch}/include'.format(
            ndk_dir             = self.ctx.ndk_dir,
            toolchain_version   = self.toolchain_version,
            arch                = arch)
        env['LDFLAGS'] += ' -L{ndk_dir}/sources/cxx-stl/gnu-libstdc++/{toolchain_version}/libs/{arch}'.format(
            ndk_dir             = self.ctx.ndk_dir,
            toolchain_version   = self.toolchain_version,
            arch                = arch)
        env['LDFLAGS'] += ' -shared'
        env['LDFLAGS'] += ' -lgnustl_shared -llog'
        env['STRIP']    = str.split(env['STRIP'])[0]

        # warning("Testing the env")
        # shprint(sh.echo, '$PATH', _env=env)
        # warning("self.ctx = {}".format(str(self.ctx)))
        # warning("self.ctx.ndk-dir = {}".format(self.ctx.ndk_dir))
        # warning("self.ctx.build_dir = {}".format(self.ctx.build_dir))
        # warning("self.ctx.libs_dir = {}".format(self.ctx.libs_dir))
        # warning("self.ctx.bootstrap.build_dir = {}".format(self.ctx.bootstrap.build_dir))
        return env
Esempio n. 10
0
    def extract_source(self, source, cwd):
        """
        (internal) Extract the `source` into the directory `cwd`.
        """
        if not source:
            return
        if isfile(source):
            info("Extract {} into {}".format(source, cwd))

            if source.endswith(".tgz") or source.endswith(".tar.gz"):
                shprint(sh.tar, "-C", cwd, "-xvzf", source)

            elif source.endswith(".tbz2") or source.endswith(".tar.bz2"):
                shprint(sh.tar, "-C", cwd, "-xvjf", source)

            elif source.endswith(".zip"):
                zf = zipfile.ZipFile(source)
                zf.extractall(path=cwd)
                zf.close()

            else:
                warning("Error: cannot extract, unrecognized extension for {}".format(source))
                raise Exception()

        elif isdir(source):
            info("Copying {} into {}".format(source, cwd))

            shprint(sh.cp, "-a", source, cwd)

        else:
            warning("Error: cannot extract or copy, unrecognized path {}".format(source))
            raise Exception()
Esempio n. 11
0
 def prebuild_arch(self, arch):
     super(VlcRecipe, self).prebuild_arch(arch)
     build_dir = self.get_build_dir(arch.arch)
     port_dir = join(build_dir, 'vlc-port-android')
     if self.ENV_LIBVLC_AAR in environ:
         aar = environ.get(self.ENV_LIBVLC_AAR)
         if isdir(aar):
             aar = join(aar, 'libvlc-{}.aar'.format(self.version))
         if not isfile(aar):
             warning("Error: {} is not valid libvlc-<ver>.aar bundle".format(aar))
             info("check {} environment!".format(self.ENV_LIBVLC_AAR))
             exit(1)
         self.aars[arch] = aar
     else:
         aar_path = join(port_dir, 'libvlc', 'build', 'outputs', 'aar')
         self.aars[arch] = aar = join(aar_path, 'libvlc-{}.aar'.format(self.version))
         warning("HINT: set path to precompiled libvlc-<ver>.aar bundle "
                 "in {} environment!".format(self.ENV_LIBVLC_AAR))
         info("libvlc-<ver>.aar should build "
              "from sources at {}".format(port_dir))
         if not isfile(join(port_dir, 'compile.sh')):
             info("clone vlc port for android sources from {}".format(
                         self.port_git))
             shprint(sh.git, 'clone', self.port_git, port_dir,
                     _tail=20, _critical=True)
Esempio n. 12
0
 def dist_dir(self):
     '''The dist dir at which to place the finished distribution.'''
     if self.distribution is None:
         warning('Tried to access {}.dist_dir, but {}.distribution '
                 'is None'.format(self, self))
         exit(1)
     return self.distribution.dist_dir
Esempio n. 13
0
    def clean_build(self, arch=None):
        '''Deletes all the build information of the recipe.

        If arch is not None, only this arch dir is deleted. Otherwise
        (the default) all builds for all archs are deleted.

        By default, this just deletes the main build dir. If the
        recipe has e.g. object files biglinked, or .so files stored
        elsewhere, you should override this method.

        This method is intended for testing purposes, it may have
        strange results. Rebuild everything if this seems to happen.

        '''
        if arch is None:
            base_dir = join(self.ctx.build_dir, 'other_builds', self.name)
        else:
            base_dir = self.get_build_container_dir(arch)
        dirs = glob.glob(base_dir + '-*')
        if exists(base_dir):
            dirs.append(base_dir)
        if not dirs:
             warning(('Attempted to clean build for {} but found no existing '
                      'build dirs').format(self.name))

        for directory in dirs:
            if exists(directory):
                info('Deleting {}'.format(directory))
                shutil.rmtree(directory)

        # Delete any Python distributions to ensure the recipe build
        # doesn't persist in site-packages
        shutil.rmtree(self.ctx.python_installs_dir)
Esempio n. 14
0
    def get_env(self):
        env = {}

        env["CFLAGS"] = " ".join([
            "-DANDROID", "-mandroid", "-fomit-frame-pointer",
            "--sysroot", self.ctx.ndk_platform])

        env["CXXFLAGS"] = env["CFLAGS"]

        env["LDFLAGS"] = " ".join(['-lm'])

        py_platform = sys.platform
        if py_platform in ['linux2', 'linux3']:
            py_platform = 'linux'

        toolchain_prefix = self.ctx.toolchain_prefix
        toolchain_version = self.ctx.toolchain_version
        command_prefix = self.command_prefix

        env['TOOLCHAIN_PREFIX'] = toolchain_prefix
        env['TOOLCHAIN_VERSION'] = toolchain_version

        print('path is', environ['PATH'])
        cc = find_executable('{command_prefix}-gcc'.format(
            command_prefix=command_prefix), path=environ['PATH'])
        if cc is None:
            warning('Couldn\'t find executable for CC. This indicates a '
                    'problem locating the {} executable in the Android '
                    'NDK, not that you don\'t have a normal compiler '
                    'installed. Exiting.')
            exit(1)

        env['CC'] = '{command_prefix}-gcc {cflags}'.format(
            command_prefix=command_prefix,
            cflags=env['CFLAGS'])
        env['CXX'] = '{command_prefix}-g++ {cxxflags}'.format(
            command_prefix=command_prefix,
            cxxflags=env['CXXFLAGS'])

        env['AR'] = '{}-ar'.format(command_prefix)
        env['RANLIB'] = '{}-ranlib'.format(command_prefix)
        env['LD'] = '{}-ld'.format(command_prefix)
        env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
        env['MAKE'] = 'make -j5'
        env['READELF'] = '{}-readelf'.format(command_prefix)

        hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)

        # AND: This hardcodes python version 2.7, needs fixing
        env['BUILDLIB_PATH'] = join(
            hostpython_recipe.get_build_dir(self.arch),
            'build', 'lib.linux-{}-2.7'.format(uname()[-1]))

        env['PATH'] = environ['PATH']

        env['ARCH'] = self.arch

        return env
Esempio n. 15
0
def check_ndk_api(ndk_api, android_api):
    """Warn if the user's NDK is too high or low."""
    if ndk_api > android_api:
        raise BuildInterruptingException(
            'Target NDK API is {}, higher than the target Android API {}.'.format(
                ndk_api, android_api),
            instructions=('The NDK API is a minimum supported API number and must be lower '
                          'than the target Android API'))

    if ndk_api < MIN_NDK_API:
        warning(OLD_NDK_API_MESSAGE)
Esempio n. 16
0
 def set_archs(self, arch_names):
     all_archs = self.archs
     new_archs = set()
     for name in arch_names:
         matching = [arch for arch in all_archs if arch.arch == name]
         for match in matching:
             new_archs.add(match)
     self.archs = list(new_archs)
     if not self.archs:
         warning('Asked to compile for no Archs, so failing.')
         exit(1)
     info('Will compile for the following archs: {}'.format(
         ', '.join([arch.arch for arch in self.archs])))
Esempio n. 17
0
def check_target_api(api, arch):
    """Warn if the user's target API is less than the current minimum
    recommendation
    """

    if api >= ARMEABI_MAX_TARGET_API and arch == 'armeabi':
        raise BuildInterruptingException(
            'Asked to build for armeabi architecture with API '
            '{}, but API {} or greater does not support armeabi'.format(
                api, ARMEABI_MAX_TARGET_API),
            instructions='You probably want to build with --arch=armeabi-v7a instead')

    if api < MIN_TARGET_API:
        warning('Target API {} < {}'.format(api, MIN_TARGET_API))
        warning(OLD_API_MESSAGE)
Esempio n. 18
0
 def strip_libraries(self, arch):
     info('Stripping libraries')
     env = arch.get_env()
     strip = which('arm-linux-androideabi-strip', env['PATH'])
     if strip is None:
         warning('Can\'t find strip in PATH...')
         return
     strip = sh.Command(strip)
     filens = shprint(sh.find, join(self.dist_dir, 'private'),
                      join(self.dist_dir, 'libs'),
                      '-iname', '*.so', _env=env).stdout.decode('utf-8')
     logger.info('Stripping libraries in private dir')
     for filen in filens.split('\n'):
         try:
             strip(filen, _env=env)
         except sh.ErrorReturnCode_1:
             logger.debug('Failed to strip ' + filen)
Esempio n. 19
0
    def download(self):
        if self.url is None:
            info('Skipping {} download as no URL is set'.format(self.name))
            return

        url = self.versioned_url

        shprint(sh.mkdir, '-p', join(self.ctx.packages_path, self.name))

        with current_directory(join(self.ctx.packages_path, self.name)):
            filename = shprint(sh.basename, url).stdout[:-1].decode('utf-8')

            do_download = True

            marker_filename = '.mark-{}'.format(filename)
            if exists(filename) and isfile(filename):
                if not exists(marker_filename):
                    shprint(sh.rm, filename)
                elif self.md5sum:
                    current_md5 = shprint(sh.md5sum, filename)
                    print('downloaded md5: {}'.format(current_md5))
                    print('expected md5: {}'.format(self.md5sum))
                    print('md5 not handled yet, exiting')
                    exit(1)
                else:
                    do_download = False
                    info('{} download already cached, skipping'
                         .format(self.name))

            # Should check headers here!
            warning('Should check headers here! Skipping for now.')

            # If we got this far, we will download
            if do_download:
                print('Downloading {} from {}'.format(self.name, url))

                shprint(sh.rm, '-f', marker_filename)
                self.download_file(url, filename)
                shprint(sh.touch, marker_filename)

                if self.md5sum is not None:
                    print('downloaded md5: {}'.format(current_md5))
                    print('expected md5: {}'.format(self.md5sum))
                    print('md5 not handled yet, exiting')
                    exit(1)
Esempio n. 20
0
    def get_pip_installed_recipe(cls, name, ctx):
        '''Attempts to get the recipe installed via pip.

        Requires the use of the "p4a_recipe" entry point. This
        entry point must return a tuple of (recipe, __file__)

        Example
        --------

        #: In p4a_myrecipe/__init__.py

            from pythonforandroid.recipe import CythonRecipe

            class MyRecipe(CythonRecipe):
                version = "1.0"
                name = "my-recipe" #: Must be defined
                # etc ...

            def get_recipe():
                return (MyRecipe(), __file__)

        #: setup.py

            setup(
              #...
              entry_points = {
                'p4a_recipe': ['my_recipe = p4a_myrecipe:get_recipe'],
              },
            )

        '''
        for ep in pkg_resources.iter_entry_points(group="p4a_recipe"):
            #: Match names with - or _ the same as entry_points requires underscores
            if ep.name.replace("-", "_") == name.replace("-", "_"):
                get_recipe = ep.load()
                warning("The '{}' recipe was loaded from a pip installed package. "\
                        "It is recommened to use P4A's builtin versions if available.".format(name))
                return get_recipe()
Esempio n. 21
0
    def clean_build(self, arch=None):
        '''Deletes all the build information of the recipe.

        If arch is not None, only this arch dir is deleted. Otherwise
        (the default) all builds for all archs are deleted.

        By default, this just deletes the main build dir. If the
        recipe has e.g. object files biglinked, or .so files stored
        elsewhere, you should override this method.

        This method is intended for testing purposes, it may have
        strange results. Rebuild everything if this seems to happen.

        '''
        if arch is None:
            dir = join(self.ctx.build_dir, 'other_builds', self.name)
        else:
            dir = self.get_build_container_dir(arch)
        if exists(dir):
            shutil.rmtree(dir)
        else:
            warning(('Attempted to clean build for {} but build '
                     'did not exist').format(self.name))
Esempio n. 22
0
    def get_distributions(cls, ctx, extra_dist_dirs=[]):
        '''Returns all the distributions found locally.'''
        if extra_dist_dirs:
            raise BuildInterruptingException(
                'extra_dist_dirs argument to get_distributions '
                'is not yet implemented')
        dist_dir = ctx.dist_dir
        folders = glob.glob(join(dist_dir, '*'))
        for dir in extra_dist_dirs:
            folders.extend(glob.glob(join(dir, '*')))

        dists = []
        for folder in folders:
            if exists(join(folder, 'dist_info.json')):
                with open(join(folder, 'dist_info.json')) as fileh:
                    dist_info = json.load(fileh)
                dist = cls(ctx)
                dist.name = folder.split('/')[-1]
                dist.dist_dir = folder
                dist.needs_build = False
                dist.recipes = dist_info['recipes']
                if 'archs' in dist_info:
                    dist.archs = dist_info['archs']
                if 'ndk_api' in dist_info:
                    dist.ndk_api = dist_info['ndk_api']
                else:
                    dist.ndk_api = None
                    warning(
                        "Distribution {distname}: ({distdir}) has been "
                        "built with an unknown api target, ignoring it, "
                        "you might want to delete it".format(
                            distname=dist.name,
                            distdir=dist.dist_dir
                        )
                    )
                dists.append(dist)
        return dists
Esempio n. 23
0
    def clean_download_cache(self, args):
        '''
        Deletes a download cache for recipes stated as arguments. If no
        argument is passed, it'll delete *all* downloaded cache. ::

            p4a clean_download_cache kivy,pyjnius

        This does *not* delete the build caches or final distributions.
        '''
        ctx = self.ctx
        if hasattr(args, 'recipes') and args.recipes:
            for package in args.recipes:
                remove_path = join(ctx.packages_path, package)
                if exists(remove_path):
                    shutil.rmtree(remove_path)
                    info('Download cache removed for: "{}"'.format(package))
                else:
                    warning('No download cache found for "{}", skipping'.format(package))
        else:
            if exists(ctx.packages_path):
                shutil.rmtree(ctx.packages_path)
                info('Download cache removed.')
            else:
                print('No cache found at "{}"'.format(ctx.packages_path))
Esempio n. 24
0
def check_ndk_version(ndk_dir):
    # Check the NDK version against what is currently recommended
    version = read_ndk_version(ndk_dir)

    if version is None:
        return  # if we failed to read the version, just don't worry about it

    major_version = version.version[0]

    info('Found NDK revision {}'.format(version))

    if major_version < MIN_NDK_VERSION:
        warning('Minimum recommended NDK version is {}'.format(
            RECOMMENDED_NDK_VERSION))
        warning(OLD_NDK_MESSAGE)
    elif major_version > MAX_NDK_VERSION:
        warning('Maximum recommended NDK version is {}'.format(
            RECOMMENDED_NDK_VERSION))
        warning(NEW_NDK_MESSAGE)
Esempio n. 25
0
    def build_arch(self, arch):
        super(MobileInsightRecipe, self).build_arch(arch)

        env = self.get_recipe_env(arch)
        # self.build_cython_components(arch)

        with current_directory(self.get_build_dir(arch.arch)):
            hostpython = sh.Command(self.ctx.hostpython)
            app_mk     = join(self.get_build_dir(arch.arch), 'Application.mk')
            app_setup  = join(self.get_build_dir(arch.arch), 'setup.py')

            if not exists(app_mk):
                shprint(sh.cp, join(self.get_recipe_dir(), 'Application.mk'), app_mk)
            if not exists(app_setup):
                shprint(sh.cp, join(self.get_recipe_dir(), 'setup.py'), app_setup)

            shprint(hostpython, 'setup.py', 'build_ext', '-v', _env = env, _tail = 10, _critical = True)
            shprint(hostpython, 'setup.py', 'install',  '-O2', _env = env, _tail = 10, _critical = True)

            build_lib = glob.glob('./build/lib*')
            assert len(build_lib) == 1
            warning('MobileInsight -- stripping mobileinsight')

            shprint(sh.find, build_lib[0], '-name', '*.so', '-exec', env['STRIP'], '{}', ';', _tail = 20, _critical = True)

        try:
            warning('Copying GNU STL shared lib to {libs_dir}/{arch}'.format(
                    libs_dir          = self.ctx.libs_dir,
                    arch              = arch))
            shprint(sh.cp,
                '{ndk_dir}/sources/cxx-stl/gnu-libstdc++/{toolchain_version}/libs/{arch}/libgnustl_shared.so'.format(
                    ndk_dir           = self.ctx.ndk_dir,
                    toolchain_version = self.toolchain_version,
                    arch              = arch),
                '{libs_dir}/{arch}'.format(
                    libs_dir          = self.ctx.libs_dir,
                    arch              = arch))
        except:
            warning('Failed to copy GNU STL shared lib!')
    def prepare_build_environment(self, user_sdk_dir, user_ndk_dir,
                                  user_android_api, user_ndk_api):
        '''Checks that build dependencies exist and sets internal variables
        for the Android SDK etc.

        ..warning:: This *must* be called before trying any build stuff

        '''

        self.ensure_dirs()

        if self._build_env_prepared:
            return

        ok = True

        # Work out where the Android SDK is
        sdk_dir = None
        if user_sdk_dir:
            sdk_dir = user_sdk_dir
        # This is the old P4A-specific var
        if sdk_dir is None:
            sdk_dir = environ.get('ANDROIDSDK', None)
        # This seems used more conventionally
        if sdk_dir is None:
            sdk_dir = environ.get('ANDROID_HOME', None)
        # Checks in the buildozer SDK dir, useful for debug tests of p4a
        if sdk_dir is None:
            possible_dirs = glob.glob(
                expanduser(
                    join('~', '.buildozer', 'android', 'platform',
                         'android-sdk-*')))
            possible_dirs = [
                d for d in possible_dirs
                if not (d.endswith('.bz2') or d.endswith('.gz'))
            ]
            if possible_dirs:
                info('Found possible SDK dirs in buildozer dir: {}'.format(
                    ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
                info('Will attempt to use SDK at {}'.format(possible_dirs[0]))
                warning('This SDK lookup is intended for debug only, if you '
                        'use python-for-android much you should probably '
                        'maintain your own SDK download.')
                sdk_dir = possible_dirs[0]
        if sdk_dir is None:
            raise BuildInterruptingException(
                'Android SDK dir was not specified, exiting.')
        self.sdk_dir = realpath(sdk_dir)

        # Check what Android API we're using
        android_api = None
        if user_android_api:
            android_api = user_android_api
            info('Getting Android API version from user argument: {}'.format(
                android_api))
        elif 'ANDROIDAPI' in environ:
            android_api = environ['ANDROIDAPI']
            info('Found Android API target in $ANDROIDAPI: {}'.format(
                android_api))
        else:
            info('Android API target was not set manually, using '
                 'the default of {}'.format(RECOMMENDED_TARGET_API))
            android_api = RECOMMENDED_TARGET_API
        android_api = int(android_api)
        self.android_api = android_api

        check_target_api(android_api, self.archs[0].arch)
        apis = get_available_apis(self.sdk_dir)
        info('Available Android APIs are ({})'.format(', '.join(map(str,
                                                                    apis))))
        if android_api in apis:
            info(('Requested API target {} is available, '
                  'continuing.').format(android_api))
        else:
            raise BuildInterruptingException(
                ('Requested API target {} is not available, install '
                 'it with the SDK android tool.').format(android_api))

        # Find the Android NDK
        # Could also use ANDROID_NDK, but doesn't look like many tools use this
        ndk_dir = None
        if user_ndk_dir:
            ndk_dir = user_ndk_dir
            info('Getting NDK dir from from user argument')
        if ndk_dir is None:  # The old P4A-specific dir
            ndk_dir = environ.get('ANDROIDNDK', None)
            if ndk_dir is not None:
                info('Found NDK dir in $ANDROIDNDK: {}'.format(ndk_dir))
        if ndk_dir is None:  # Apparently the most common convention
            ndk_dir = environ.get('NDK_HOME', None)
            if ndk_dir is not None:
                info('Found NDK dir in $NDK_HOME: {}'.format(ndk_dir))
        if ndk_dir is None:  # Another convention (with maven?)
            ndk_dir = environ.get('ANDROID_NDK_HOME', None)
            if ndk_dir is not None:
                info('Found NDK dir in $ANDROID_NDK_HOME: {}'.format(ndk_dir))
        if ndk_dir is None:  # Checks in the buildozer NDK dir, useful
            #                # for debug tests of p4a
            possible_dirs = glob.glob(
                expanduser(
                    join('~', '.buildozer', 'android', 'platform',
                         'android-ndk-r*')))
            if possible_dirs:
                info('Found possible NDK dirs in buildozer dir: {}'.format(
                    ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
                info('Will attempt to use NDK at {}'.format(possible_dirs[0]))
                warning('This NDK lookup is intended for debug only, if you '
                        'use python-for-android much you should probably '
                        'maintain your own NDK download.')
                ndk_dir = possible_dirs[0]
        if ndk_dir is None:
            raise BuildInterruptingException(
                'Android NDK dir was not specified')
        self.ndk_dir = realpath(ndk_dir)

        check_ndk_version(ndk_dir)

        ndk_api = None
        if user_ndk_api:
            ndk_api = user_ndk_api
            info(
                'Getting NDK API version (i.e. minimum supported API) from user argument'
            )
        elif 'NDKAPI' in environ:
            ndk_api = environ.get('NDKAPI', None)
            info('Found Android API target in $NDKAPI')
        else:
            ndk_api = min(self.android_api, RECOMMENDED_NDK_API)
            warning(
                'NDK API target was not set manually, using '
                'the default of {} = min(android-api={}, default ndk-api={})'.
                format(ndk_api, self.android_api, RECOMMENDED_NDK_API))
        ndk_api = int(ndk_api)
        self.ndk_api = ndk_api

        check_ndk_api(ndk_api, self.android_api)

        virtualenv = get_virtualenv_executable()
        if virtualenv is None:
            raise IOError('Couldn\'t find a virtualenv executable, '
                          'you must install this to use p4a.')
        self.virtualenv = virtualenv
        info('Found virtualenv at {}'.format(virtualenv))

        # path to some tools
        self.ccache = sh.which("ccache")
        if not self.ccache:
            info('ccache is missing, the build will not be optimized in the '
                 'future.')
        try:
            subprocess.check_output([
                "python3",
                "-m",
                "cython",
                "--help",
            ])
        except subprocess.CalledProcessError:
            warning('Cython for python3 missing. If you are building for '
                    ' a python 3 target (which is the default)'
                    ' then THINGS WILL BREAK.')

        # This would need to be changed if supporting multiarch APKs
        arch = self.archs[0]
        toolchain_prefix = arch.toolchain_prefix
        self.ndk_platform, ndk_platform_dir_exists = get_ndk_platform_dir(
            self.ndk_dir, self.ndk_api, arch)
        ok = ok and ndk_platform_dir_exists

        py_platform = sys.platform
        if py_platform in ['linux2', 'linux3']:
            py_platform = 'linux'
        toolchain_versions, toolchain_path_exists = get_toolchain_versions(
            self.ndk_dir, arch)
        ok = ok and toolchain_path_exists
        toolchain_versions.sort()

        toolchain_versions_gcc = []
        for toolchain_version in toolchain_versions:
            if toolchain_version[0].isdigit():
                # GCC toolchains begin with a number
                toolchain_versions_gcc.append(toolchain_version)

        if toolchain_versions:
            info('Found the following toolchain versions: {}'.format(
                toolchain_versions))
            info('Picking the latest gcc toolchain, here {}'.format(
                toolchain_versions_gcc[-1]))
            toolchain_version = toolchain_versions_gcc[-1]
        else:
            warning('Could not find any toolchain for {}!'.format(
                toolchain_prefix))
            ok = False

        self.toolchain_prefix = toolchain_prefix
        self.toolchain_version = toolchain_version
        # Modify the path so that sh finds modules appropriately
        environ['PATH'] = (
            '{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/'
            'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/'
            '{toolchain_prefix}-{toolchain_version}/prebuilt/'
            '{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/'
            'tools:{path}').format(sdk_dir=self.sdk_dir,
                                   ndk_dir=self.ndk_dir,
                                   toolchain_prefix=toolchain_prefix,
                                   toolchain_version=toolchain_version,
                                   py_platform=py_platform,
                                   path=environ.get('PATH'))

        for executable in ("pkg-config", "autoconf", "automake", "libtoolize",
                           "tar", "bzip2", "unzip", "make", "gcc", "g++"):
            if not sh.which(executable):
                warning("Missing executable: {} is not installed".format(
                    executable))

        if not ok:
            raise BuildInterruptingException(
                'python-for-android cannot continue due to the missing executables above'
            )
Esempio n. 27
0
    def prepare_build_environment(self, user_sdk_dir, user_ndk_dir,
                                  user_android_api, user_ndk_ver):
        '''Checks that build dependencies exist and sets internal variables
        for the Android SDK etc.

        ..warning:: This *must* be called before trying any build stuff

        '''

        self.ensure_dirs()

        if self._build_env_prepared:
            return

        # AND: This needs revamping to carefully check each dependency
        # in turn
        ok = True

        # Work out where the Android SDK is
        sdk_dir = None
        if user_sdk_dir:
            sdk_dir = user_sdk_dir
        if sdk_dir is None:  # This is the old P4A-specific var
            sdk_dir = environ.get('ANDROIDSDK', None)
        if sdk_dir is None:  # This seems used more conventionally
            sdk_dir = environ.get('ANDROID_HOME', None)
        if sdk_dir is None:  # Checks in the buildozer SDK dir, useful
            #                # for debug tests of p4a
            possible_dirs = glob.glob(expanduser(join(
                '~', '.buildozer', 'android', 'platform', 'android-sdk-*')))
            if possible_dirs:
                info('Found possible SDK dirs in buildozer dir: {}'.format(
                    ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
                info('Will attempt to use SDK at {}'.format(possible_dirs[0]))
                warning('This SDK lookup is intended for debug only, if you '
                        'use python-for-android much you should probably '
                        'maintain your own SDK download.')
                sdk_dir = possible_dirs[0]
        if sdk_dir is None:
            warning('Android SDK dir was not specified, exiting.')
            exit(1)
        self.sdk_dir = realpath(sdk_dir)

        # Check what Android API we're using
        android_api = None
        if user_android_api:
            android_api = user_android_api
            if android_api is not None:
                info('Getting Android API version from user argument')
        if android_api is None:
            android_api = environ.get('ANDROIDAPI', None)
            if android_api is not None:
                info('Found Android API target in $ANDROIDAPI')
        if android_api is None:
            info('Android API target was not set manually, using '
                 'the default of {}'.format(DEFAULT_ANDROID_API))
            android_api = DEFAULT_ANDROID_API
        android_api = int(android_api)
        self.android_api = android_api

        if self.android_api >= 21 and self.archs[0].arch == 'armeabi':
            error('Asked to build for armeabi architecture with API '
                  '{}, but API 21 or greater does not support armeabi'.format(
                      self.android_api))
            error('You probably want to build with --arch=armeabi-v7a instead')
            exit(1)

        android = sh.Command(join(sdk_dir, 'tools', 'android'))
        targets = android('list').stdout.decode('utf-8').split('\n')
        apis = [s for s in targets if re.match(r'^ *API level: ', s)]
        apis = [re.findall(r'[0-9]+', s) for s in apis]
        apis = [int(s[0]) for s in apis if s]
        info('Available Android APIs are ({})'.format(
            ', '.join(map(str, apis))))
        if android_api in apis:
            info(('Requested API target {} is available, '
                  'continuing.').format(android_api))
        else:
            warning(('Requested API target {} is not available, install '
                     'it with the SDK android tool.').format(android_api))
            warning('Exiting.')
            exit(1)

        # Find the Android NDK
        # Could also use ANDROID_NDK, but doesn't look like many tools use this
        ndk_dir = None
        if user_ndk_dir:
            ndk_dir = user_ndk_dir
            if ndk_dir is not None:
                info('Getting NDK dir from from user argument')
        if ndk_dir is None:  # The old P4A-specific dir
            ndk_dir = environ.get('ANDROIDNDK', None)
            if ndk_dir is not None:
                info('Found NDK dir in $ANDROIDNDK')
        if ndk_dir is None:  # Apparently the most common convention
            ndk_dir = environ.get('NDK_HOME', None)
            if ndk_dir is not None:
                info('Found NDK dir in $NDK_HOME')
        if ndk_dir is None:  # Another convention (with maven?)
            ndk_dir = environ.get('ANDROID_NDK_HOME', None)
            if ndk_dir is not None:
                info('Found NDK dir in $ANDROID_NDK_HOME')
        if ndk_dir is None:  # Checks in the buildozer NDK dir, useful
            #                # for debug tests of p4a
            possible_dirs = glob.glob(expanduser(join(
                '~', '.buildozer', 'android', 'platform', 'android-ndk-r*')))
            if possible_dirs:
                info('Found possible NDK dirs in buildozer dir: {}'.format(
                    ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
                info('Will attempt to use NDK at {}'.format(possible_dirs[0]))
                warning('This NDK lookup is intended for debug only, if you '
                        'use python-for-android much you should probably '
                        'maintain your own NDK download.')
                ndk_dir = possible_dirs[0]
        if ndk_dir is None:
            warning('Android NDK dir was not specified, exiting.')
            exit(1)
        self.ndk_dir = realpath(ndk_dir)

        # Find the NDK version, and check it against what the NDK dir
        # seems to report
        ndk_ver = None
        if user_ndk_ver:
            ndk_ver = user_ndk_ver
            if ndk_dir is not None:
                info('Got NDK version from from user argument')
        if ndk_ver is None:
            ndk_ver = environ.get('ANDROIDNDKVER', None)
            if ndk_dir is not None:
                info('Got NDK version from $ANDROIDNDKVER')

        self.ndk = 'google'

        try:
            with open(join(ndk_dir, 'RELEASE.TXT')) as fileh:
                reported_ndk_ver = fileh.read().split(' ')[0].strip()
        except IOError:
            pass
        else:
            if reported_ndk_ver.startswith('crystax-ndk-'):
                reported_ndk_ver = reported_ndk_ver[12:]
                self.ndk = 'crystax'
            if ndk_ver is None:
                ndk_ver = reported_ndk_ver
                info(('Got Android NDK version from the NDK dir: '
                      'it is {}').format(ndk_ver))
            else:
                if ndk_ver != reported_ndk_ver:
                    warning('NDK version was set as {}, but checking '
                            'the NDK dir claims it is {}.'.format(
                                ndk_ver, reported_ndk_ver))
                    warning('The build will try to continue, but it may '
                            'fail and you should check '
                            'that your setting is correct.')
                    warning('If the NDK dir result is correct, you don\'t '
                            'need to manually set the NDK ver.')
        if ndk_ver is None:
            warning('Android NDK version could not be found, exiting.')
        self.ndk_ver = ndk_ver

        info('Using {} NDK {}'.format(self.ndk.capitalize(), self.ndk_ver))

        virtualenv = None
        if virtualenv is None:
            virtualenv = sh.which('virtualenv2')
        if virtualenv is None:
            virtualenv = sh.which('virtualenv-2.7')
        if virtualenv is None:
            virtualenv = sh.which('virtualenv')
        if virtualenv is None:
            raise IOError('Couldn\'t find a virtualenv executable, '
                          'you must install this to use p4a.')
        self.virtualenv = virtualenv
        info('Found virtualenv at {}'.format(virtualenv))

        # path to some tools
        self.ccache = sh.which("ccache")
        if not self.ccache:
            info('ccache is missing, the build will not be optimized in the '
                 'future.')
        for cython_fn in ("cython2", "cython-2.7", "cython"):
            cython = sh.which(cython_fn)
            if cython:
                self.cython = cython
                break
        else:
            error('No cython binary found. Exiting.')
            exit(1)
        if not self.cython:
            ok = False
            warning("Missing requirement: cython is not installed")

        # AND: need to change if supporting multiple archs at once
        arch = self.archs[0]
        platform_dir = arch.platform_dir
        toolchain_prefix = arch.toolchain_prefix
        self.ndk_platform = join(
            self.ndk_dir,
            'platforms',
            'android-{}'.format(self.android_api),
            platform_dir)
        if not exists(self.ndk_platform):
            warning('ndk_platform doesn\'t exist: {}'.format(
                self.ndk_platform))
            ok = False

        py_platform = sys.platform
        if py_platform in ['linux2', 'linux3']:
            py_platform = 'linux'

        toolchain_versions = []
        toolchain_path = join(self.ndk_dir, 'toolchains')
        if os.path.isdir(toolchain_path):
            toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
                                                            toolchain_prefix))
            toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
                                  for path in toolchain_contents]
        else:
            warning('Could not find toolchain subdirectory!')
            ok = False
        toolchain_versions.sort()

        toolchain_versions_gcc = []
        for toolchain_version in toolchain_versions:
            if toolchain_version[0].isdigit():
                # GCC toolchains begin with a number
                toolchain_versions_gcc.append(toolchain_version)

        if toolchain_versions:
            info('Found the following toolchain versions: {}'.format(
                toolchain_versions))
            info('Picking the latest gcc toolchain, here {}'.format(
                toolchain_versions_gcc[-1]))
            toolchain_version = toolchain_versions_gcc[-1]
        else:
            warning('Could not find any toolchain for {}!'.format(
                toolchain_prefix))
            ok = False

        self.toolchain_prefix = toolchain_prefix
        self.toolchain_version = toolchain_version
        # Modify the path so that sh finds modules appropriately
        environ['PATH'] = (
            '{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/'
            'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/'
            '{toolchain_prefix}-{toolchain_version}/prebuilt/'
            '{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/'
            'tools:{path}').format(
                sdk_dir=self.sdk_dir, ndk_dir=self.ndk_dir,
                toolchain_prefix=toolchain_prefix,
                toolchain_version=toolchain_version,
                py_platform=py_platform, path=environ.get('PATH'))

        for executable in ("pkg-config", "autoconf", "automake", "libtoolize",
                           "tar", "bzip2", "unzip", "make", "gcc", "g++"):
            if not sh.which(executable):
                warning("Missing executable: {} is not installed".format(
                    executable))

        if not ok:
            error('{}python-for-android cannot continue; aborting{}'.format(
                Err_Fore.RED, Err_Fore.RESET))
            sys.exit(1)
Esempio n. 28
0
def get_recipe_order_and_bootstrap(ctx, names, bs=None):
    '''Takes a list of recipe names and (optionally) a bootstrap. Then
    works out the dependency graph (including bootstrap recipes if
    necessary). Finally, if no bootstrap was initially selected,
    chooses one that supports all the recipes.
    '''
    graph = Graph()
    recipes_to_load = set(names)
    if bs is not None and bs.recipe_depends:
        info_notify('Bootstrap requires recipes {}'.format(bs.recipe_depends))
        recipes_to_load = recipes_to_load.union(set(bs.recipe_depends))
    recipes_to_load = list(recipes_to_load)
    recipe_loaded = []
    python_modules = []
    while recipes_to_load:
        name = recipes_to_load.pop(0)
        if name in recipe_loaded or isinstance(name, (list, tuple)):
            continue
        try:
            recipe = Recipe.get_recipe(name, ctx)
        except IOError:
            info('No recipe named {}; will attempt to install with pip'.format(
                name))
            python_modules.append(name)
            continue
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            warning('Failed to import recipe named {}; the recipe exists '
                    'but appears broken.'.format(name))
            warning('Exception was:')
            raise
        graph.add(name, name)
        info('Loaded recipe {} (depends on {}{})'.format(
            name, recipe.depends, ', conflicts {}'.format(recipe.conflicts)
            if recipe.conflicts else ''))
        for depend in recipe.depends:
            graph.add(name, depend)
            recipes_to_load += recipe.depends
        for conflict in recipe.conflicts:
            if graph.conflicts(conflict):
                warning(('{} conflicts with {}, but both have been '
                         'included or pulled into the requirements.'.format(
                             recipe.name, conflict)))
                warning(
                    'Due to this conflict the build cannot continue, exiting.')
                exit(1)
        python_modules += recipe.python_depends
        recipe_loaded.append(name)
    graph.remove_remaining_conflicts(ctx)
    if len(graph.graphs) > 1:
        info('Found multiple valid recipe sets:')
        for g in graph.graphs:
            info('    {}'.format(g.keys()))
        info_notify('Using the first of these: {}'.format(
            graph.graphs[0].keys()))
    elif len(graph.graphs) == 0:
        warning('Didn\'t find any valid dependency graphs, exiting.')
        exit(1)
    else:
        info('Found a single valid recipe set (this is good)')

    build_order = list(graph.find_order(0))
    if bs is None:  # It would be better to check against possible
        # orders other than the first one, but in practice
        # there will rarely be clashes, and the user can
        # specify more parameters if necessary to resolve
        # them.
        bs = Bootstrap.get_bootstrap_from_recipes(build_order, ctx)
        if bs is None:
            info('Could not find a bootstrap compatible with the '
                 'required recipes.')
            info('If you think such a combination should exist, try '
                 'specifying the bootstrap manually with --bootstrap.')
            exit(1)
        info('{} bootstrap appears compatible with the required recipes.'.
             format(bs.name))
        info('Checking this...')
        recipes_to_load = bs.recipe_depends
        # This code repeats the code from earlier! Should move to a function:
        while recipes_to_load:
            name = recipes_to_load.pop(0)
            if name in recipe_loaded or isinstance(name, (list, tuple)):
                continue
            try:
                recipe = Recipe.get_recipe(name, ctx)
            except ImportError:
                info('No recipe named {}; will attempt to install with pip'.
                     format(name))
                python_modules.append(name)
                continue
            graph.add(name, name)
            info('Loaded recipe {} (depends on {}{})'.format(
                name, recipe.depends, ', conflicts {}'.format(recipe.conflicts)
                if recipe.conflicts else ''))
            for depend in recipe.depends:
                graph.add(name, depend)
                recipes_to_load += recipe.depends
            for conflict in recipe.conflicts:
                if graph.conflicts(conflict):
                    warning(
                        ('{} conflicts with {}, but both have been '
                         'included or pulled into the requirements.'.format(
                             recipe.name, conflict)))
                    warning('Due to this conflict the build cannot continue, '
                            'exiting.')
                    exit(1)
            recipe_loaded.append(name)
        graph.remove_remaining_conflicts(ctx)
        build_order = list(graph.find_order(0))
    return build_order, python_modules, bs
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 __init__(self):

        argv = sys.argv
        # Buildozer used to pass these arguments in a now-invalid order
        # If that happens, apply this fix
        # This fix will be removed once a fixed buildozer is released
        if (len(argv) > 2 and
            argv[1].startswith('--color') and
            argv[2].startswith('--storage-dir')):
            argv.append(argv.pop(1))  # the --color arg
            argv.append(argv.pop(1))  # the --storage-dir arg

        parser = NoAbbrevParser(
            description=('A packaging tool for turning Python scripts and apps '
                         'into Android APKs'))

        generic_parser = argparse.ArgumentParser(
            add_help=False,
            description=('Generic arguments applied to all commands'))
        dist_parser = argparse.ArgumentParser(
            add_help=False,
            description=('Arguments for dist building'))

        generic_parser.add_argument(
            '--debug', dest='debug', action='store_true',
            default=False,
            help='Display debug output and all build info')
        generic_parser.add_argument(
            '--color', dest='color', choices=['always', 'never', 'auto'],
            help='Enable or disable color output (default enabled on tty)')
        generic_parser.add_argument(
            '--sdk-dir', '--sdk_dir', dest='sdk_dir', default='',
            help='The filepath where the Android SDK is installed')
        generic_parser.add_argument(
            '--ndk-dir', '--ndk_dir', dest='ndk_dir', default='',
            help='The filepath where the Android NDK is installed')
        generic_parser.add_argument(
            '--android-api', '--android_api', dest='android_api', default=0, type=int,
            help='The Android API level to build against.')
        generic_parser.add_argument(
            '--ndk-version', '--ndk_version', dest='ndk_version', default='',
            help=('The version of the Android NDK. This is optional, '
                  'we try to work it out automatically from the ndk_dir.'))
        generic_parser.add_argument(
            '--symlink-java-src', '--symlink_java_src',
            action='store_true',
            dest='symlink_java_src',
            default=False,
            help=('If True, symlinks the java src folder during build and dist '
                  'creation. This is useful for development only, it could also '
                  'cause weird problems.'))

        default_storage_dir = user_data_dir('python-for-android')
        if ' ' in default_storage_dir:
            default_storage_dir = '~/.python-for-android'
        generic_parser.add_argument(
            '--storage-dir', dest='storage_dir',
            default=default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(default_storage_dir)))

        # AND: This option doesn't really fit in the other categories, the
        # arg structure needs a rethink
        generic_parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi')

        # Options for specifying the Distribution
        generic_parser.add_argument(
            '--dist-name', '--dist_name',
            help='The name of the distribution to use or create',
            default='')

        generic_parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules'),
            default='')
        
        generic_parser.add_argument(
            '--bootstrap',
            help='The bootstrap to build with. Leave unset to choose automatically.',
            default=None)

        add_boolean_option(
            generic_parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution:')

        generic_parser.add_argument(
            '--extra-dist-dirs', '--extra_dist_dirs',
            dest='extra_dist_dirs', default='',
            help='Directories in which to look for distributions')

        add_boolean_option(
            generic_parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        generic_parser.add_argument(
            '--local-recipes', '--local_recipes',
            dest='local_recipes', default='./p4a-recipes',
            help='Directory to look for local recipes')

        add_boolean_option(
            generic_parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)')

        self._read_configuration()

        subparsers = parser.add_subparsers(dest='subparser_name',
                                           help='The command to run')

        def add_parser(subparsers, *args, **kwargs):
            '''
            argparse in python2 doesn't support the aliases option,
            so we just don't provide the aliases there.
            '''
            if 'aliases' in kwargs and sys.version_info.major < 3:
                kwargs.pop('aliases')
            return subparsers.add_parser(*args, **kwargs)

        parser_recipes = add_parser(subparsers,
            'recipes',
            parents=[generic_parser],
            help='List the available recipes')
        parser_recipes.add_argument(
                "--compact", action="store_true", default=False,
                help="Produce a compact list suitable for scripting")

        parser_bootstraps = add_parser(subparsers,
            'bootstraps', help='List the available bootstraps',
            parents=[generic_parser])
        parser_clean_all = add_parser(subparsers,
            'clean_all', aliases=['clean-all'],
            help='Delete all builds, dists and caches',
            parents=[generic_parser])
        parser_clean_dists = add_parser(subparsers,
            'clean_dists', aliases=['clean-dists'],
            help='Delete all dists',
            parents=[generic_parser])
        parser_clean_bootstrap_builds = add_parser(subparsers,
            'clean_bootstrap_builds', aliases=['clean-bootstrap-builds'],
            help='Delete all bootstrap builds',
            parents=[generic_parser])
        parser_clean_builds = add_parser(subparsers,
            'clean_builds', aliases=['clean-builds'],
            help='Delete all builds',
            parents=[generic_parser])

        parser_clean_recipe_build = add_parser(subparsers,
            'clean_recipe_build', aliases=['clean-recipe-build'],
            help=('Delete the build components of the given recipe. '
                  'By default this will also delete built dists'),
            parents=[generic_parser])
        parser_clean_recipe_build.add_argument('recipe', help='The recipe name')
        parser_clean_recipe_build.add_argument('--no-clean-dists', default=False,
                                               dest='no_clean_dists',
                                               action='store_true',
                                               help='If passed, do not delete existing dists')

        parser_clean_download_cache= add_parser(subparsers,
            'clean_download_cache', aliases=['clean-download-cache'],
            help='Delete cached downloads for requirement builds',
            parents=[generic_parser])
        parser_clean_download_cache.add_argument(
            'recipes', nargs='*',
            help=('The recipes to clean (space-separated). If no recipe name is '
                  'provided, the entire cache is cleared.'))

        parser_export_dist = add_parser(subparsers,
            'export_dist', aliases=['export-dist'],
            help='Copy the named dist to the given path',
            parents=[generic_parser])
        parser_export_dist.add_argument('output_dir', help=('The output dir to copy to'))
        parser_export_dist.add_argument('--symlink', action='store_true',
                                        help=('Symlink the dist instead of copying'))

        parser_apk = add_parser(subparsers,
            'apk', help='Build an APK',
            parents=[generic_parser])
        parser_apk.add_argument('--release', dest='build_mode', action='store_const',
                        const='release', default='debug',
                        help='Build the PARSER_APK. in Release mode')
        parser_apk.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)'))
        parser_apk.add_argument('--signkey', dest='signkey', action='store', default=None,
                        help='Key alias to sign PARSER_APK. with (release build only)')
        parser_apk.add_argument('--keystorepw', dest='keystorepw', action='store', default=None,
                        help='Password for keystore')
        parser_apk.add_argument('--signkeypw', dest='signkeypw', action='store', default=None,
                        help='Password for key alias')

        parser_create = add_parser(subparsers,
            'create', help='Compile a set of requirements into a dist',
            parents=[generic_parser])
        parser_archs = add_parser(subparsers,
            'archs', help='List the available target architectures',
            parents=[generic_parser])
        parser_distributions = add_parser(subparsers,
            'distributions', aliases=['dists'],
            help='List the currently available (compiled) dists',
            parents=[generic_parser])
        parser_delete_dist = add_parser(subparsers,
            'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',
            parents=[generic_parser])

        parser_sdk_tools = add_parser(subparsers,
            'sdk_tools', aliases=['sdk-tools'],
            help='Run the given binary from the SDK tools dis',
            parents=[generic_parser])
        parser_sdk_tools.add_argument(
            'tool', help=('The tool binary name to run'))

        parser_adb = add_parser(subparsers,
            'adb', help='Run adb from the given SDK',
            parents=[generic_parser])
        parser_logcat = add_parser(subparsers,
            'logcat', help='Run logcat from the given SDK',
            parents=[generic_parser])
        parser_build_status = add_parser(subparsers,
            'build_status', aliases=['build-status'],
            help='Print some debug information about current built components',
            parents=[generic_parser])

        args, unknown = parser.parse_known_args(sys.argv[1:])
        args.unknown_args = unknown

        self.args = args

        setup_color(args.color)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        # strip version from requirements, and put them in environ
        if hasattr(args, 'requirements'):
            requirements = []
            for requirement in split_argument_list(args.requirements):
                if "==" in requirement:
                    requirement, version = requirement.split(u"==", 1)
                    os.environ["VERSION_{}".format(requirement)] = version
                    info('Recipe {}: version "{}" requested'.format(
                        requirement, version))
                requirements.append(requirement)
            args.requirements = u",".join(requirements)

        self.ctx = Context()
        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_version = args.ndk_version
        self.ctx.symlink_java_src = args.symlink_java_src

        self._archs = split_argument_list(args.arch)

        # AND: Fail nicely if the args aren't handled yet
        if args.extra_dist_dirs:
            warning('Received --extra_dist_dirs but this arg currently is not '
                    'handled, exiting.')
            exit(1)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        # Each subparser corresponds to a method
        getattr(self, args.subparser_name.replace('-', '_'))(args)
Esempio n. 31
0
    def get_env(self, with_flags_in_cc=True):
        env = {}

        env["CFLAGS"] = " ".join([
            "-DANDROID", "-mandroid", "-fomit-frame-pointer", "--sysroot",
            self.ctx.ndk_platform
        ])

        env["CXXFLAGS"] = env["CFLAGS"]

        env["LDFLAGS"] = " ".join(
            ['-lm', '-L' + self.ctx.get_libs_dir(self.arch)])

        if self.ctx.ndk == 'crystax':
            env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(
                self.ctx.ndk_dir, self.arch)

        py_platform = sys.platform
        if py_platform in ['linux2', 'linux3']:
            py_platform = 'linux'

        toolchain_prefix = self.ctx.toolchain_prefix
        toolchain_version = self.ctx.toolchain_version
        command_prefix = self.command_prefix

        env['TOOLCHAIN_PREFIX'] = toolchain_prefix
        env['TOOLCHAIN_VERSION'] = toolchain_version

        ccache = ''
        if self.ctx.ccache and bool(int(environ.get('USE_CCACHE', '1'))):
            print('ccache found, will optimize builds')
            ccache = self.ctx.ccache + ' '
            env['USE_CCACHE'] = '1'
            env['NDK_CCACHE'] = self.ctx.ccache
            env.update(
                {k: v
                 for k, v in environ.items() if k.startswith('CCACHE_')})

        print('path is', environ['PATH'])
        cc = find_executable(
            '{command_prefix}-gcc'.format(command_prefix=command_prefix),
            path=environ['PATH'])
        if cc is None:
            warning('Couldn\'t find executable for CC. This indicates a '
                    'problem locating the {} executable in the Android '
                    'NDK, not that you don\'t have a normal compiler '
                    'installed. Exiting.')
            exit(1)

        if with_flags_in_cc:
            env['CC'] = '{ccache}{command_prefix}-gcc {cflags}'.format(
                command_prefix=command_prefix,
                ccache=ccache,
                cflags=env['CFLAGS'])
            env['CXX'] = '{ccache}{command_prefix}-g++ {cxxflags}'.format(
                command_prefix=command_prefix,
                ccache=ccache,
                cxxflags=env['CXXFLAGS'])
        else:
            env['CC'] = '{ccache}{command_prefix}-gcc'.format(
                command_prefix=command_prefix, ccache=ccache)
            env['CXX'] = '{ccache}{command_prefix}-g++'.format(
                command_prefix=command_prefix, ccache=ccache)

        env['AR'] = '{}-ar'.format(command_prefix)
        env['RANLIB'] = '{}-ranlib'.format(command_prefix)
        env['LD'] = '{}-ld'.format(command_prefix)
        # env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
        # env['LDSHARED'] = env['LD']
        env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
        env['MAKE'] = 'make -j5'
        env['READELF'] = '{}-readelf'.format(command_prefix)
        env['NM'] = '{}-nm'.format(command_prefix)

        hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)

        # AND: This hardcodes python version 2.7, needs fixing
        env['BUILDLIB_PATH'] = join(hostpython_recipe.get_build_dir(self.arch),
                                    'build',
                                    'lib.linux-{}-2.7'.format(uname()[-1]))

        env['PATH'] = environ['PATH']

        env['ARCH'] = self.arch

        if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax:
            env['CRYSTAX_PYTHON_VERSION'] = self.ctx.python_recipe.version

        return env
Esempio n. 32
0
 def build_arch(self, arch):
     warning('DEPRECATION: Support for the Python 2 recipe will be '
             'removed in 2020, please upgrade to Python 3.')
     super().build_arch(arch)
Esempio n. 33
0
 def build_arch(self, arch):
     warning('PIL is no longer supported, building Pillow instead. '
             'This should be a drop-in replacement.')
     warning('It is recommended to change "pil" to "pillow" in your requirements, '
             'to ensure future compatibility')
     super().build_arch(arch)
Esempio n. 34
0
    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))
Esempio n. 35
0
def check_ndk_version(ndk_dir):
    """
    Check the NDK version against what is currently recommended and raise an
    exception of :class:`~pythonforandroid.util.BuildInterruptingException` in
    case that the user tries to use an NDK lower than minimum supported,
    specified via attribute `MIN_NDK_VERSION`.

    .. versionchanged:: 2019.06.06.1.dev0
        Added the ability to get android's NDK `letter version` and also
        rewrote to raise an exception in case that an NDK version lower than
        the minimum supported is detected.
    """
    version = read_ndk_version(ndk_dir)

    if version is None:
        warning(READ_ERROR_NDK_MESSAGE.format(ndk_dir=ndk_dir))
        warning(
            ENSURE_RIGHT_NDK_MESSAGE.format(
                min_supported=MIN_NDK_VERSION,
                rec_version=RECOMMENDED_NDK_VERSION,
                ndk_url=NDK_DOWNLOAD_URL,
            )
        )
        return

    # create a dictionary which will describe the relationship of the android's
    # NDK minor version with the `human readable` letter version, egs:
    # Pkg.Revision = 17.1.4828580 => ndk-17b
    # Pkg.Revision = 17.2.4988734 => ndk-17c
    # Pkg.Revision = 19.0.5232133 => ndk-19 (No letter)
    minor_to_letter = {0: ''}
    minor_to_letter.update(
        {n + 1: chr(i) for n, i in enumerate(range(ord('b'), ord('b') + 25))}
    )

    major_version = version.version[0]
    letter_version = minor_to_letter[version.version[1]]
    string_version = '{major_version}{letter_version}'.format(
        major_version=major_version, letter_version=letter_version
    )

    info(CURRENT_NDK_VERSION_MESSAGE.format(ndk_version=string_version))

    if major_version < MIN_NDK_VERSION:
        raise BuildInterruptingException(
            NDK_LOWER_THAN_SUPPORTED_MESSAGE.format(
                min_supported=MIN_NDK_VERSION, ndk_url=NDK_DOWNLOAD_URL
            ),
            instructions=(
                'Please, go to the android NDK page ({ndk_url}) and download a'
                ' supported version.\n*** The currently recommended NDK'
                ' version is {rec_version} ***'.format(
                    ndk_url=NDK_DOWNLOAD_URL,
                    rec_version=RECOMMENDED_NDK_VERSION,
                )
            ),
        )
    elif major_version > MAX_NDK_VERSION:
        warning(
            RECOMMENDED_NDK_VERSION_MESSAGE.format(
                recommended_ndk_version=RECOMMENDED_NDK_VERSION
            )
        )
        warning(NEW_NDK_MESSAGE)
Esempio n. 36
0
    def warn_on_deprecated_args(self, args):
        """
        Print warning messages for any deprecated arguments that were passed.
        """

        # Output warning if setup.py is present and neither --ignore-setup-py
        # nor --use-setup-py was specified.
        if getattr(args, "private", None) is not None and \
                (os.path.exists(os.path.join(args.private, "setup.py")) or
                 os.path.exists(os.path.join(args.private, "pyproject.toml"))
                ):
            if not getattr(args, "use_setup_py", False) and \
                    not getattr(args, "ignore_setup_py", False):
                warning("  **** FUTURE BEHAVIOR CHANGE WARNING ****")
                warning("Your project appears to contain a setup.py file.")
                warning("Currently, these are ignored by default.")
                warning("This will CHANGE in an upcoming version!")
                warning("")
                warning("To ensure your setup.py is ignored, please specify:")
                warning("    --ignore-setup-py")
                warning("")
                warning(
                    "To enable what will some day be the default, specify:")
                warning("    --use-setup-py")

        # NDK version is now determined automatically
        if args.ndk_version is not None:
            warning('--ndk-version is deprecated and no longer necessary, '
                    'the value you passed is ignored')
        if 'ANDROIDNDKVER' in environ:
            warning('$ANDROIDNDKVER is deprecated and no longer necessary, '
                    'the value you set is ignored')
Esempio n. 37
0
    def get_env(self, with_flags_in_cc=True):
        env = {}

        env['CFLAGS'] = ' '.join([
            '-DANDROID', '-mandroid', '-fomit-frame-pointer'
            ' -D__ANDROID_API__={}'.format(self.ctx.ndk_api),
            ])
        env['LDFLAGS'] = ' '

        sysroot = join(self.ctx._ndk_dir, 'sysroot')
        if exists(sysroot):
            # post-15 NDK per
            # https://android.googlesource.com/platform/ndk/+/ndk-r15-release/docs/UnifiedHeaders.md
            env['CFLAGS'] += ' -isystem {}/sysroot/usr/include/{}'.format(
                self.ctx.ndk_dir, self.ctx.toolchain_prefix)
        else:
            sysroot = self.ctx.ndk_platform
            env['CFLAGS'] += ' -I{}'.format(self.ctx.ndk_platform)
        env['CFLAGS'] += ' -isysroot {} '.format(sysroot)
        env['CFLAGS'] += '-I' + join(self.ctx.get_python_install_dir(),
                                     'include/python{}'.format(
                                         self.ctx.python_recipe.version[0:3])
                                    )

        env['LDFLAGS'] += '--sysroot {} '.format(self.ctx.ndk_platform)

        env["CXXFLAGS"] = env["CFLAGS"]

        env["LDFLAGS"] += " ".join(['-lm', '-L' + self.ctx.get_libs_dir(self.arch)])

        if self.ctx.ndk == 'crystax':
            env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(self.ctx.ndk_dir, self.arch)

        py_platform = sys.platform
        if py_platform in ['linux2', 'linux3']:
            py_platform = 'linux'

        toolchain_prefix = self.ctx.toolchain_prefix
        toolchain_version = self.ctx.toolchain_version
        command_prefix = self.command_prefix

        env['TOOLCHAIN_PREFIX'] = toolchain_prefix
        env['TOOLCHAIN_VERSION'] = toolchain_version

        ccache = ''
        if self.ctx.ccache and bool(int(environ.get('USE_CCACHE', '1'))):
            # print('ccache found, will optimize builds')
            ccache = self.ctx.ccache + ' '
            env['USE_CCACHE'] = '1'
            env['NDK_CCACHE'] = self.ctx.ccache
            env.update({k: v for k, v in environ.items() if k.startswith('CCACHE_')})

        cc = find_executable('{command_prefix}-gcc'.format(
            command_prefix=command_prefix), path=environ['PATH'])
        if cc is None:
            print('Searching path are: {!r}'.format(environ['PATH']))
            warning('Couldn\'t find executable for CC. This indicates a '
                    'problem locating the {} executable in the Android '
                    'NDK, not that you don\'t have a normal compiler '
                    'installed. Exiting.')
            exit(1)

        if with_flags_in_cc:
            env['CC'] = '{ccache}{command_prefix}-gcc {cflags}'.format(
                command_prefix=command_prefix,
                ccache=ccache,
                cflags=env['CFLAGS'])
            env['CXX'] = '{ccache}{command_prefix}-g++ {cxxflags}'.format(
                command_prefix=command_prefix,
                ccache=ccache,
                cxxflags=env['CXXFLAGS'])
        else:
            env['CC'] = '{ccache}{command_prefix}-gcc'.format(
                command_prefix=command_prefix,
                ccache=ccache)
            env['CXX'] = '{ccache}{command_prefix}-g++'.format(
                command_prefix=command_prefix,
                ccache=ccache)

        env['AR'] = '{}-ar'.format(command_prefix)
        env['RANLIB'] = '{}-ranlib'.format(command_prefix)
        env['LD'] = '{}-ld'.format(command_prefix)
        env['LDSHARED'] = env["CC"] + " -pthread -shared " +\
            "-Wl,-O1 -Wl,-Bsymbolic-functions "
        if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax:
            # For crystax python, we can't use the host python headers:
            env["CFLAGS"] += ' -I{}/sources/python/{}/include/python/'.\
                format(self.ctx.ndk_dir, self.ctx.python_recipe.version[0:3])
        env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
        env['MAKE'] = 'make -j5'
        env['READELF'] = '{}-readelf'.format(command_prefix)
        env['NM'] = '{}-nm'.format(command_prefix)

        hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)

        # This hardcodes python version 2.7, needs fixing
        env['BUILDLIB_PATH'] = join(
            hostpython_recipe.get_build_dir(self.arch),
            'build', 'lib.linux-{}-2.7'.format(uname()[-1]))

        env['PATH'] = environ['PATH']

        env['ARCH'] = self.arch
        env['NDK_API'] = 'android-{}'.format(str(self.ctx.ndk_api))

        if self.ctx.python_recipe and self.ctx.python_recipe.from_crystax:
            env['CRYSTAX_PYTHON_VERSION'] = self.ctx.python_recipe.version

        return env
Esempio n. 38
0
    def __init__(self):
        self._ctx = None

        parser = argparse.ArgumentParser(
            description="Tool for managing the Android / Python toolchain",
            usage="""toolchain <command> [<args>]

Available commands:
adb           Runs adb binary from the detected SDK dir
apk           Create an APK using the given distribution
bootstraps    List all the bootstraps available to build with.
build_status  Informations about the current build
create        Build an android project with all recipes
clean_all     Delete all build components
clean_builds  Delete all build caches
clean_dists   Delete all compiled distributions
clean_download_cache Delete any downloaded recipe packages
clean_recipe_build   Delete the build files of a recipe
distributions List all distributions
export_dist   Copies a created dist to an output directory
logcat        Runs logcat from the detected SDK dir
print_context_info   Prints debug informations
recipes       List all the available recipes
sdk_tools     Runs android binary from the detected SDK dir
symlink_dist  Symlinks a created dist to an output directory

Planned commands:
build_dist
""")
        parser.add_argument("command", help="Command to run")

        # General options
        parser.add_argument('--debug',
                            dest='debug',
                            action='store_true',
                            help='Display debug output and all build info')
        parser.add_argument(
            '--sdk_dir',
            dest='sdk_dir',
            default='',
            help='The filepath where the Android SDK is installed')
        parser.add_argument(
            '--ndk_dir',
            dest='ndk_dir',
            default='',
            help='The filepath where the Android NDK is installed')
        parser.add_argument('--android_api',
                            dest='android_api',
                            default=0,
                            type=int,
                            help='The Android API level to build against.')
        parser.add_argument(
            '--ndk_version',
            dest='ndk_version',
            default='',
            help=('The version of the Android NDK. This is optional, '
                  'we try to work it out automatically from the ndk_dir.'))

        # AND: This option doesn't really fit in the other categories, the
        # arg structure needs a rethink
        parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi')

        # Options for specifying the Distribution
        parser.add_argument(
            '--dist_name',
            help='The name of the distribution to use or create',
            default='')
        parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules'),
            default='')

        add_boolean_option(
            parser, ["allow-download"],
            default=False,
            description='Whether to allow binary dist download:')

        add_boolean_option(
            parser, ["allow-build"],
            default=True,
            description='Whether to allow compilation of a new distribution:')

        add_boolean_option(
            parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution:')

        parser.add_argument(
            '--extra-dist-dirs',
            '--extra_dist_dirs',
            dest='extra_dist_dirs',
            default='',
            help='Directories in which to look for distributions')

        add_boolean_option(
            parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        parser.add_argument('--local-recipes',
                            '--local_recipes',
                            dest='local_recipes',
                            default='./p4a-recipes',
                            help='Directory to look for local recipes')

        self._read_configuration()

        args, unknown = parser.parse_known_args(sys.argv[1:])
        self.dist_args = args

        if args.debug:
            logger.setLevel(logging.DEBUG)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_version = args.ndk_version

        self._archs = split_argument_list(args.arch)

        # AND: Fail nicely if the args aren't handled yet
        if args.extra_dist_dirs:
            warning('Received --extra_dist_dirs but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        if args.allow_download:
            warning('Received --allow_download but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        # if args.allow_build:
        #     warning('Received --allow_build but this arg currently is not '
        #             'handled, exiting.')
        #     exit(1)

        if not hasattr(self, args.command):
            print('Unrecognized command')
            parser.print_help()
            exit(1)

        self.ctx.local_recipes = args.local_recipes

        getattr(self, args.command)(unknown)
Esempio n. 39
0
    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 self.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)) and not self.force_build:
                info('copy in place skipping cp {0}'.format(self.user_dir))
                return
            else:
                info('clening dirs')
                shprint(sh.rm, '-rf', build_dir)
                shprint(sh.mkdir, '-p', build_dir)
                shprint(sh.rmdir, build_dir)
                ensure_dir(build_dir)
                info('starting cp {0}'.format(self.user_dir))
                shprint(sh.cp, '-a', self.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))
Esempio n. 40
0
    def __init__(self):
        parser = argparse.ArgumentParser(
            description="Tool for managing the Android / Python toolchain",
            usage="""toolchain <command> [<args>]

Available commands:
adb           Runs adb binary from the detected SDK dir
apk           Create an APK using the given distribution
bootstraps    List all the bootstraps available to build with.
build_status  Informations about the current build
create        Build an android project with all recipes
clean_all     Delete all build components
clean_builds  Delete all build caches
clean_dists   Delete all compiled distributions
clean_download_cache Delete any downloaded recipe packages
clean_recipe_build   Delete the build files of a recipe
distributions List all distributions
export_dist   Copies a created dist to an output directory
logcat        Runs logcat from the detected SDK dir
print_context_info   Prints debug informations
recipes       List all the available recipes
sdk_tools     Runs android binary from the detected SDK dir
symlink_dist  Symlinks a created dist to an output directory

Planned commands:
build_dist
""")
        parser.add_argument("command", help="Command to run")

        # General options
        parser.add_argument('--debug',
                            dest='debug',
                            action='store_true',
                            help='Display debug output and all build info')
        parser.add_argument(
            '--color',
            dest='color',
            choices=['always', 'never', 'auto'],
            help='Enable or disable color output (default enabled on tty)')
        parser.add_argument(
            '--sdk-dir',
            '--sdk_dir',
            dest='sdk_dir',
            default='',
            help='The filepath where the Android SDK is installed')
        parser.add_argument(
            '--ndk-dir',
            '--ndk_dir',
            dest='ndk_dir',
            default='',
            help='The filepath where the Android NDK is installed')
        parser.add_argument('--android-api',
                            '--android_api',
                            dest='android_api',
                            default=0,
                            type=int,
                            help='The Android API level to build against.')
        parser.add_argument(
            '--ndk-version',
            '--ndk_version',
            dest='ndk_version',
            default='',
            help=('The version of the Android NDK. This is optional, '
                  'we try to work it out automatically from the ndk_dir.'))
        parser.add_argument(
            '--storage-dir',
            dest='storage_dir',
            default=self.default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(self.default_storage_dir)))

        # AND: This option doesn't really fit in the other categories, the
        # arg structure needs a rethink
        parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi')

        # Options for specifying the Distribution
        parser.add_argument(
            '--dist-name',
            '--dist_name',
            help='The name of the distribution to use or create',
            default='')
        parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules'),
            default='')

        add_boolean_option(
            parser, ["allow-download"],
            default=False,
            description='Whether to allow binary dist download:')

        add_boolean_option(
            parser, ["allow-build"],
            default=True,
            description='Whether to allow compilation of a new distribution:')

        add_boolean_option(
            parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution:')

        parser.add_argument(
            '--extra-dist-dirs',
            '--extra_dist_dirs',
            dest='extra_dist_dirs',
            default='',
            help='Directories in which to look for distributions')

        add_boolean_option(
            parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        parser.add_argument('--local-recipes',
                            '--local_recipes',
                            dest='local_recipes',
                            default='./p4a-recipes',
                            help='Directory to look for local recipes')

        add_boolean_option(
            parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)'
        )

        self._read_configuration()

        args, unknown = parser.parse_known_args(sys.argv[1:])
        self.dist_args = args

        setup_color(args.color)

        info(''.join([
            Err_Style.BRIGHT, Err_Fore.RED,
            'This python-for-android revamp is an experimental alpha release!',
            Err_Style.RESET_ALL
        ]))
        info(''.join([
            Err_Fore.RED,
            ('It should work (mostly), but you may experience '
             'missing features or bugs.'), Err_Style.RESET_ALL
        ]))

        # strip version from requirements, and put them in environ
        requirements = []
        for requirement in split_argument_list(args.requirements):
            if "==" in requirement:
                requirement, version = requirement.split(u"==", 1)
                os.environ["VERSION_{}".format(requirement)] = version
                info('Recipe {}: version "{}" requested'.format(
                    requirement, version))
            requirements.append(requirement)
        args.requirements = u",".join(requirements)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        self.ctx = Context()
        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_version = args.ndk_version

        self._archs = split_argument_list(args.arch)

        # AND: Fail nicely if the args aren't handled yet
        if args.extra_dist_dirs:
            warning('Received --extra_dist_dirs but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        if args.allow_download:
            warning('Received --allow_download but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        # if args.allow_build:
        #     warning('Received --allow_build but this arg currently is not '
        #             'handled, exiting.')
        #     exit(1)

        if not hasattr(self, args.command):
            print('Unrecognized command')
            parser.print_help()
            exit(1)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        getattr(self, args.command)(unknown)
Esempio n. 41
0
    def __init__(self):
        parser = argparse.ArgumentParser(
                description="Tool for managing the Android / Python toolchain",
                usage="""toolchain <command> [<args>]

Available commands:
adb           Runs adb binary from the detected SDK dir
apk           Create an APK using the given distribution
bootstraps    List all the bootstraps available to build with.
build_status  Informations about the current build
create        Build an android project with all recipes
clean_all     Delete all build components
clean_builds  Delete all build caches
clean_dists   Delete all compiled distributions
clean_download_cache Delete any downloaded recipe packages
clean_recipe_build   Delete the build files of a recipe
distributions List all distributions
export_dist   Copies a created dist to an output directory
logcat        Runs logcat from the detected SDK dir
print_context_info   Prints debug informations
recipes       List all the available recipes
sdk_tools     Runs android binary from the detected SDK dir
symlink_dist  Symlinks a created dist to an output directory

Planned commands:
build_dist
""")
        parser.add_argument("command", help="Command to run")

        # General options
        parser.add_argument(
            '--debug', dest='debug', action='store_true',
            help='Display debug output and all build info')
        parser.add_argument(
            '--sdk-dir', '--sdk_dir', dest='sdk_dir', default='',
            help='The filepath where the Android SDK is installed')
        parser.add_argument(
            '--ndk-dir', '--ndk_dir', dest='ndk_dir', default='',
            help='The filepath where the Android NDK is installed')
        parser.add_argument(
            '--android-api', '--android_api', dest='android_api', default=0, type=int,
            help='The Android API level to build against.')
        parser.add_argument(
            '--ndk-version', '--ndk_version', dest='ndk_version', default='',
            help=('The version of the Android NDK. This is optional, '
                  'we try to work it out automatically from the ndk_dir.'))
        parser.add_argument(
            '--storage-dir', dest='storage_dir',
            default=self.default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(self.default_storage_dir)))

        # AND: This option doesn't really fit in the other categories, the
        # arg structure needs a rethink
        parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi')

        # Options for specifying the Distribution
        parser.add_argument(
            '--dist-name', '--dist_name',
            help='The name of the distribution to use or create',
            default='')
        parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules'),
            default='')

        add_boolean_option(
            parser, ["allow-download"],
            default=False,
            description='Whether to allow binary dist download:')

        add_boolean_option(
            parser, ["allow-build"],
            default=True,
            description='Whether to allow compilation of a new distribution:')

        add_boolean_option(
            parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution:')

        parser.add_argument(
            '--extra-dist-dirs', '--extra_dist_dirs',
            dest='extra_dist_dirs', default='',
            help='Directories in which to look for distributions')

        add_boolean_option(
            parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        parser.add_argument(
            '--local-recipes', '--local_recipes',
            dest='local_recipes', default='./p4a-recipes',
            help='Directory to look for local recipes')

        add_boolean_option(
            parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)')

        self._read_configuration()

        args, unknown = parser.parse_known_args(sys.argv[1:])
        self.dist_args = args

        # strip version from requirements, and put them in environ
        requirements = []
        for requirement in split_argument_list(args.requirements):
            if "==" in requirement:
                requirement, version = requirement.split(u"==", 1)
                os.environ["VERSION_{}".format(requirement)] = version
                info('Recipe {}: version "{}" requested'.format(
                    requirement, version))
            requirements.append(requirement)
        args.requirements = u",".join(requirements)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        self.ctx = Context()
        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_version = args.ndk_version

        self._archs = split_argument_list(args.arch)

        # AND: Fail nicely if the args aren't handled yet
        if args.extra_dist_dirs:
            warning('Received --extra_dist_dirs but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        if args.allow_download:
            warning('Received --allow_download but this arg currently is not '
                    'handled, exiting.')
            exit(1)
        # if args.allow_build:
        #     warning('Received --allow_build but this arg currently is not '
        #             'handled, exiting.')
        #     exit(1)

        if not hasattr(self, args.command):
            print('Unrecognized command')
            parser.print_help()
            exit(1)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        getattr(self, args.command)(unknown)
Esempio n. 42
0
    def build_arch(self, arch):
        super(MobileInsightRecipe, self).build_arch(arch)

        env = self.get_recipe_env(arch)
        # self.build_cython_components(arch)

        with current_directory(self.get_build_dir(arch.arch)):
            hostpython = sh.Command(self.ctx.hostpython)
            app_mk = join(self.get_build_dir(arch.arch), 'Application.mk')
            app_setup = join(self.get_build_dir(arch.arch), 'setup.py')

            if not exists(app_mk):
                shprint(sh.cp, join(self.get_recipe_dir(), 'Application.mk'),
                        app_mk)
            if not exists(app_setup):
                shprint(sh.cp, join(self.get_recipe_dir(), 'setup.py'),
                        app_setup)

            shprint(hostpython,
                    'setup.py',
                    'build_ext',
                    '-v',
                    _env=env,
                    _tail=10,
                    _critical=True)
            shprint(hostpython,
                    'setup.py',
                    'install',
                    '-O2',
                    '--root={}'.format(self.ctx.get_python_install_dir()),
                    '--install-lib=.',
                    _env=env,
                    _tail=10,
                    _critical=True)

            build_lib = glob.glob('./build/lib*')
            assert len(build_lib) == 1
            warning('MobileInsight -- stripping mobileinsight')

            shprint(sh.find,
                    build_lib[0],
                    '-name',
                    '*.so',
                    '-exec',
                    env['STRIP'],
                    '{}',
                    ';',
                    _tail=20,
                    _critical=True)

        try:
            warning('Copying LLVM libc++ STL shared lib to {libs_dir}/{arch}'.
                    format(libs_dir=self.ctx.libs_dir, arch=arch))

            shprint(
                sh.cp,
                '{ndk_dir}/sources/cxx-stl/llvm-libc++/libs/{arch}/libc++_shared.so'
                .format(ndk_dir=self.ctx.ndk_dir,
                        toolchain_version=self.toolchain_version,
                        arch=arch),
                '{libs_dir}/{arch}'.format(libs_dir=self.ctx.libs_dir,
                                           arch=arch))
        except:
            warning('Failed to copy LLVM libc++ STL shared lib!')
Esempio n. 43
0
    def __init__(self):

        argv = sys.argv
        self.warn_on_carriage_return_args(argv)
        # Buildozer used to pass these arguments in a now-invalid order
        # If that happens, apply this fix
        # This fix will be removed once a fixed buildozer is released
        if (len(argv) > 2 and argv[1].startswith('--color')
                and argv[2].startswith('--storage-dir')):
            argv.append(argv.pop(1))  # the --color arg
            argv.append(argv.pop(1))  # the --storage-dir arg

        parser = NoAbbrevParser(
            description='A packaging tool for turning Python scripts and apps '
            'into Android APKs')

        generic_parser = argparse.ArgumentParser(
            add_help=False,
            description='Generic arguments applied to all commands')
        argparse.ArgumentParser(add_help=False,
                                description='Arguments for dist building')

        generic_parser.add_argument(
            '--debug',
            dest='debug',
            action='store_true',
            default=False,
            help='Display debug output and all build info')
        generic_parser.add_argument(
            '--color',
            dest='color',
            choices=['always', 'never', 'auto'],
            help='Enable or disable color output (default enabled on tty)')
        generic_parser.add_argument(
            '--sdk-dir',
            '--sdk_dir',
            dest='sdk_dir',
            default='',
            help='The filepath where the Android SDK is installed')
        generic_parser.add_argument(
            '--ndk-dir',
            '--ndk_dir',
            dest='ndk_dir',
            default='',
            help='The filepath where the Android NDK is installed')
        generic_parser.add_argument(
            '--android-api',
            '--android_api',
            dest='android_api',
            default=0,
            type=int,
            help=('The Android API level to build against defaults to {} if '
                  'not specified.').format(RECOMMENDED_TARGET_API))
        generic_parser.add_argument(
            '--ndk-version',
            '--ndk_version',
            dest='ndk_version',
            default=None,
            help=('DEPRECATED: the NDK version is now found automatically or '
                  'not at all.'))
        generic_parser.add_argument(
            '--ndk-api',
            type=int,
            default=None,
            help=
            ('The Android API level to compile against. This should be your '
             '*minimal supported* API, not normally the same as your --android-api. '
             'Defaults to min(ANDROID_API, {}) if not specified.'
             ).format(RECOMMENDED_NDK_API))
        generic_parser.add_argument(
            '--symlink-java-src',
            '--symlink_java_src',
            action='store_true',
            dest='symlink_java_src',
            default=False,
            help=(
                'If True, symlinks the java src folder during build and dist '
                'creation. This is useful for development only, it could also'
                ' cause weird problems.'))

        default_storage_dir = user_data_dir('python-for-android')
        if ' ' in default_storage_dir:
            default_storage_dir = '~/.python-for-android'
        generic_parser.add_argument(
            '--storage-dir',
            dest='storage_dir',
            default=default_storage_dir,
            help=('Primary storage directory for downloads and builds '
                  '(default: {})'.format(default_storage_dir)))

        generic_parser.add_argument(
            '--arch',
            help='The archs to build for, separated by commas.',
            default='armeabi-v7a')

        # Options for specifying the Distribution
        generic_parser.add_argument(
            '--dist-name',
            '--dist_name',
            help='The name of the distribution to use or create',
            default='')

        generic_parser.add_argument(
            '--requirements',
            help=('Dependencies of your app, should be recipe names or '
                  'Python modules. NOT NECESSARY if you are using '
                  'Python 3 with --use-setup-py'),
            default='')

        generic_parser.add_argument(
            '--recipe-blacklist',
            help=('Blacklist an internal recipe from use. Allows '
                  'disabling Python 3 core modules to save size'),
            dest="recipe_blacklist",
            default='')

        generic_parser.add_argument(
            '--blacklist-requirements',
            help=('Blacklist an internal recipe from use. Allows '
                  'disabling Python 3 core modules to save size'),
            dest="blacklist_requirements",
            default='')

        generic_parser.add_argument(
            '--bootstrap',
            help='The bootstrap to build with. Leave unset to choose '
            'automatically.',
            default=None)

        generic_parser.add_argument(
            '--hook',
            help='Filename to a module that contains python-for-android hooks',
            default=None)

        add_boolean_option(
            generic_parser, ["force-build"],
            default=False,
            description='Whether to force compilation of a new distribution')

        add_boolean_option(
            generic_parser, ["require-perfect-match"],
            default=False,
            description=('Whether the dist recipes must perfectly match '
                         'those requested'))

        add_boolean_option(
            generic_parser, ["allow-replace-dist"],
            default=True,
            description=
            'Whether existing dist names can be automatically replaced')

        generic_parser.add_argument('--local-recipes',
                                    '--local_recipes',
                                    dest='local_recipes',
                                    default='./p4a-recipes',
                                    help='Directory to look for local recipes')

        generic_parser.add_argument(
            '--java-build-tool',
            dest='java_build_tool',
            default='auto',
            choices=['auto', 'ant', 'gradle'],
            help=(
                'The java build tool to use when packaging the APK, defaults '
                'to automatically selecting an appropriate tool.'))

        add_boolean_option(
            generic_parser, ['copy-libs'],
            default=False,
            description='Copy libraries instead of using biglink (Android 4.3+)'
        )

        self._read_configuration()

        subparsers = parser.add_subparsers(dest='subparser_name',
                                           help='The command to run')

        def add_parser(subparsers, *args, **kwargs):
            """
            argparse in python2 doesn't support the aliases option,
            so we just don't provide the aliases there.
            """
            if 'aliases' in kwargs and sys.version_info.major < 3:
                kwargs.pop('aliases')
            return subparsers.add_parser(*args, **kwargs)

        add_parser(subparsers,
                   'recommendations',
                   parents=[generic_parser],
                   help='List recommended p4a dependencies')
        parser_recipes = add_parser(subparsers,
                                    'recipes',
                                    parents=[generic_parser],
                                    help='List the available recipes')
        parser_recipes.add_argument(
            "--compact",
            action="store_true",
            default=False,
            help="Produce a compact list suitable for scripting")
        add_parser(subparsers,
                   'bootstraps',
                   help='List the available bootstraps',
                   parents=[generic_parser])
        add_parser(subparsers,
                   'clean_all',
                   aliases=['clean-all'],
                   help='Delete all builds, dists and caches',
                   parents=[generic_parser])
        add_parser(subparsers,
                   'clean_dists',
                   aliases=['clean-dists'],
                   help='Delete all dists',
                   parents=[generic_parser])
        add_parser(subparsers,
                   'clean_bootstrap_builds',
                   aliases=['clean-bootstrap-builds'],
                   help='Delete all bootstrap builds',
                   parents=[generic_parser])
        add_parser(subparsers,
                   'clean_builds',
                   aliases=['clean-builds'],
                   help='Delete all builds',
                   parents=[generic_parser])

        parser_clean = add_parser(subparsers,
                                  'clean',
                                  help='Delete build components.',
                                  parents=[generic_parser])
        parser_clean.add_argument(
            'component',
            nargs='+',
            help=('The build component(s) to delete. You can pass any '
                  'number of arguments from "all", "builds", "dists", '
                  '"distributions", "bootstrap_builds", "downloads".'))

        parser_clean_recipe_build = add_parser(
            subparsers,
            'clean_recipe_build',
            aliases=['clean-recipe-build'],
            help=('Delete the build components of the given recipe. '
                  'By default this will also delete built dists'),
            parents=[generic_parser])
        parser_clean_recipe_build.add_argument('recipe',
                                               help='The recipe name')
        parser_clean_recipe_build.add_argument(
            '--no-clean-dists',
            default=False,
            dest='no_clean_dists',
            action='store_true',
            help='If passed, do not delete existing dists')

        parser_clean_download_cache = add_parser(
            subparsers,
            'clean_download_cache',
            aliases=['clean-download-cache'],
            help='Delete cached downloads for requirement builds',
            parents=[generic_parser])
        parser_clean_download_cache.add_argument(
            'recipes',
            nargs='*',
            help='The recipes to clean (space-separated). If no recipe name is'
            ' provided, the entire cache is cleared.')

        parser_export_dist = add_parser(
            subparsers,
            'export_dist',
            aliases=['export-dist'],
            help='Copy the named dist to the given path',
            parents=[generic_parser])
        parser_export_dist.add_argument('output_dir',
                                        help='The output dir to copy to')
        parser_export_dist.add_argument(
            '--symlink',
            action='store_true',
            help='Symlink the dist instead of copying')

        parser_apk = add_parser(subparsers,
                                'apk',
                                help='Build an APK',
                                parents=[generic_parser])
        # This is actually an internal argument of the build.py
        # (see pythonforandroid/bootstraps/common/build/build.py).
        # However, it is also needed before the distribution is finally
        # assembled for locating the setup.py / other build systems, which
        # is why we also add it here:
        parser_apk.add_argument(
            '--private',
            dest='private',
            help='the directory with the app source code files' +
            ' (containing your main.py entrypoint)',
            required=False,
            default=None)
        parser_apk.add_argument('--release',
                                dest='build_mode',
                                action='store_const',
                                const='release',
                                default='debug',
                                help='Build the PARSER_APK. in Release mode')
        parser_apk.add_argument(
            '--use-setup-py',
            dest="use_setup_py",
            action='store_true',
            default=False,
            help="Process the setup.py of a project if present. " +
            "(Experimental!")
        parser_apk.add_argument(
            '--ignore-setup-py',
            dest="ignore_setup_py",
            action='store_true',
            default=False,
            help="Don't run the setup.py of a project if present. " +
            "This may be required if the setup.py is not " +
            "designed to work inside p4a (e.g. by installing " +
            "dependencies that won't work or aren't desired " + "on Android")
        parser_apk.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)'))
        parser_apk.add_argument(
            '--signkey',
            dest='signkey',
            action='store',
            default=None,
            help='Key alias to sign PARSER_APK. with (release build only)')
        parser_apk.add_argument('--keystorepw',
                                dest='keystorepw',
                                action='store',
                                default=None,
                                help='Password for keystore')
        parser_apk.add_argument('--signkeypw',
                                dest='signkeypw',
                                action='store',
                                default=None,
                                help='Password for key alias')

        add_parser(subparsers,
                   'create',
                   help='Compile a set of requirements into a dist',
                   parents=[generic_parser])
        add_parser(subparsers,
                   'archs',
                   help='List the available target architectures',
                   parents=[generic_parser])
        add_parser(subparsers,
                   'distributions',
                   aliases=['dists'],
                   help='List the currently available (compiled) dists',
                   parents=[generic_parser])
        add_parser(subparsers,
                   'delete_dist',
                   aliases=['delete-dist'],
                   help='Delete a compiled dist',
                   parents=[generic_parser])

        parser_sdk_tools = add_parser(
            subparsers,
            'sdk_tools',
            aliases=['sdk-tools'],
            help='Run the given binary from the SDK tools dis',
            parents=[generic_parser])
        parser_sdk_tools.add_argument('tool',
                                      help='The binary tool name to run')

        add_parser(subparsers,
                   'adb',
                   help='Run adb from the given SDK',
                   parents=[generic_parser])
        add_parser(subparsers,
                   'logcat',
                   help='Run logcat from the given SDK',
                   parents=[generic_parser])
        add_parser(
            subparsers,
            'build_status',
            aliases=['build-status'],
            help='Print some debug information about current built components',
            parents=[generic_parser])

        parser.add_argument('-v',
                            '--version',
                            action='version',
                            version=__version__)

        args, unknown = parser.parse_known_args(sys.argv[1:])
        args.unknown_args = unknown
        if hasattr(args, "private") and args.private is not None:
            # Pass this value on to the internal bootstrap build.py:
            args.unknown_args += ["--private", args.private]
        if hasattr(args, "ignore_setup_py") and args.ignore_setup_py:
            args.use_setup_py = False

        self.args = args

        if args.subparser_name is None:
            parser.print_help()
            exit(1)

        setup_color(args.color)

        if args.debug:
            logger.setLevel(logging.DEBUG)

        self.ctx = Context()
        self.ctx.use_setup_py = getattr(args, "use_setup_py", True)

        have_setup_py_or_similar = False
        if getattr(args, "private", None) is not None:
            project_dir = getattr(args, "private")
            if (os.path.exists(os.path.join(project_dir, "setup.py"))
                    or os.path.exists(
                        os.path.join(project_dir, "pyproject.toml"))):
                have_setup_py_or_similar = True

        # Process requirements and put version in environ
        if hasattr(args, 'requirements'):
            requirements = []

            # Add dependencies from setup.py, but only if they are recipes
            # (because otherwise, setup.py itself will install them later)
            if (have_setup_py_or_similar
                    and getattr(args, "use_setup_py", False)):
                try:
                    info("Analyzing package dependencies. MAY TAKE A WHILE.")
                    # Get all the dependencies corresponding to a recipe:
                    dependencies = [
                        dep.lower() for dep in get_dep_names_of_package(
                            args.private,
                            keep_version_pins=True,
                            recursive=True,
                            verbose=True,
                        )
                    ]
                    info("Dependencies obtained: " + str(dependencies))
                    all_recipes = [
                        recipe.lower()
                        for recipe in set(Recipe.list_recipes(self.ctx))
                    ]
                    dependencies = set(dependencies).intersection(
                        set(all_recipes))
                    # Add dependencies to argument list:
                    if len(dependencies) > 0:
                        if len(args.requirements) > 0:
                            args.requirements += u","
                        args.requirements += u",".join(dependencies)
                except ValueError:
                    # Not a python package, apparently.
                    warning("Processing failed, is this project a valid "
                            "package? Will continue WITHOUT setup.py deps.")

            # Parse --requirements argument list:
            for requirement in split_argument_list(args.requirements):
                if "==" in requirement:
                    requirement, version = requirement.split(u"==", 1)
                    os.environ["VERSION_{}".format(requirement)] = version
                    info('Recipe {}: version "{}" requested'.format(
                        requirement, version))
                requirements.append(requirement)
            args.requirements = u",".join(requirements)

        self.warn_on_deprecated_args(args)

        self.storage_dir = args.storage_dir
        self.ctx.setup_dirs(self.storage_dir)
        self.sdk_dir = args.sdk_dir
        self.ndk_dir = args.ndk_dir
        self.android_api = args.android_api
        self.ndk_api = args.ndk_api
        self.ctx.symlink_java_src = args.symlink_java_src
        self.ctx.java_build_tool = args.java_build_tool

        self._archs = split_argument_list(args.arch)

        self.ctx.local_recipes = args.local_recipes
        self.ctx.copy_libs = args.copy_libs

        # Each subparser corresponds to a method
        getattr(self, args.subparser_name.replace('-', '_'))(args)
Esempio n. 44
0
    def postbuild_arch(self, arch):
        super(MobileInsightRecipe, self).postbuild_arch(arch)

        # TODO
        warning('Should remove mobileinsight build tools here, skipping for now')
Esempio n. 45
0
    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))
Esempio n. 46
0
    def prepare_build_environment(self, user_sdk_dir, user_ndk_dir,
                                  user_android_api, user_ndk_ver):
        '''Checks that build dependencies exist and sets internal variables
        for the Android SDK etc.

        ..warning:: This *must* be called before trying any build stuff

        '''

        self.ensure_dirs()

        if self._build_env_prepared:
            return

        ok = True

        # Work out where the Android SDK is
        sdk_dir = None
        if user_sdk_dir:
            sdk_dir = user_sdk_dir
        if sdk_dir is None:  # This is the old P4A-specific var
            sdk_dir = environ.get('ANDROIDSDK', None)
        if sdk_dir is None:  # This seems used more conventionally
            sdk_dir = environ.get('ANDROID_HOME', None)
        if sdk_dir is None:  # Checks in the buildozer SDK dir, useful
            # for debug tests of p4a
            possible_dirs = glob.glob(
                expanduser(
                    join('~', '.buildozer', 'android', 'platform',
                         'android-sdk-*')))
            possible_dirs = [
                d for d in possible_dirs
                if not (d.endswith('.bz2') or d.endswith('.gz'))
            ]
            if possible_dirs:
                info('Found possible SDK dirs in buildozer dir: {}'.format(
                    ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
                info('Will attempt to use SDK at {}'.format(possible_dirs[0]))
                warning('This SDK lookup is intended for debug only, if you '
                        'use python-for-android much you should probably '
                        'maintain your own SDK download.')
                sdk_dir = possible_dirs[0]
        if sdk_dir is None:
            warning('Android SDK dir was not specified, exiting.')
            exit(1)
        self.sdk_dir = realpath(sdk_dir)

        # Check what Android API we're using
        android_api = None
        if user_android_api:
            android_api = user_android_api
            if android_api is not None:
                info('Getting Android API version from user argument')
        if android_api is None:
            android_api = environ.get('ANDROIDAPI', None)
            if android_api is not None:
                info('Found Android API target in $ANDROIDAPI')
        if android_api is None:
            info('Android API target was not set manually, using '
                 'the default of {}'.format(DEFAULT_ANDROID_API))
            android_api = DEFAULT_ANDROID_API
        android_api = int(android_api)
        self.android_api = android_api

        if self.android_api >= 21 and self.archs[0].arch == 'armeabi':
            error('Asked to build for armeabi architecture with API '
                  '{}, but API 21 or greater does not support armeabi'.format(
                      self.android_api))
            error('You probably want to build with --arch=armeabi-v7a instead')
            exit(1)

        if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
            avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin',
                                         'avdmanager'))
            targets = avdmanager('list',
                                 'target').stdout.decode('utf-8').split('\n')
        elif exists(join(sdk_dir, 'tools', 'android')):
            android = sh.Command(join(sdk_dir, 'tools', 'android'))
            targets = android('list').stdout.decode('utf-8').split('\n')
        else:
            error('Could not find `android` or `sdkmanager` binaries in '
                  'Android SDK. Exiting.')
            exit(1)
        apis = [s for s in targets if re.match(r'^ *API level: ', s)]
        apis = [re.findall(r'[0-9]+', s) for s in apis]
        apis = [int(s[0]) for s in apis if s]
        info('Available Android APIs are ({})'.format(', '.join(map(str,
                                                                    apis))))
        if android_api in apis:
            info(('Requested API target {} is available, '
                  'continuing.').format(android_api))
        else:
            warning(('Requested API target {} is not available, install '
                     'it with the SDK android tool.').format(android_api))
            warning('Exiting.')
            exit(1)

        # Find the Android NDK
        # Could also use ANDROID_NDK, but doesn't look like many tools use this
        ndk_dir = None
        if user_ndk_dir:
            ndk_dir = user_ndk_dir
            if ndk_dir is not None:
                info('Getting NDK dir from from user argument')
        if ndk_dir is None:  # The old P4A-specific dir
            ndk_dir = environ.get('ANDROIDNDK', None)
            if ndk_dir is not None:
                info('Found NDK dir in $ANDROIDNDK')
        if ndk_dir is None:  # Apparently the most common convention
            ndk_dir = environ.get('NDK_HOME', None)
            if ndk_dir is not None:
                info('Found NDK dir in $NDK_HOME')
        if ndk_dir is None:  # Another convention (with maven?)
            ndk_dir = environ.get('ANDROID_NDK_HOME', None)
            if ndk_dir is not None:
                info('Found NDK dir in $ANDROID_NDK_HOME')
        if ndk_dir is None:  # Checks in the buildozer NDK dir, useful
            #                # for debug tests of p4a
            possible_dirs = glob.glob(
                expanduser(
                    join('~', '.buildozer', 'android', 'platform',
                         'android-ndk-r*')))
            if possible_dirs:
                info('Found possible NDK dirs in buildozer dir: {}'.format(
                    ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
                info('Will attempt to use NDK at {}'.format(possible_dirs[0]))
                warning('This NDK lookup is intended for debug only, if you '
                        'use python-for-android much you should probably '
                        'maintain your own NDK download.')
                ndk_dir = possible_dirs[0]
        if ndk_dir is None:
            warning('Android NDK dir was not specified, exiting.')
            exit(1)
        self.ndk_dir = realpath(ndk_dir)

        # Find the NDK version, and check it against what the NDK dir
        # seems to report
        ndk_ver = None
        if user_ndk_ver:
            ndk_ver = user_ndk_ver
            if ndk_dir is not None:
                info('Got NDK version from from user argument')
        if ndk_ver is None:
            ndk_ver = environ.get('ANDROIDNDKVER', None)
            if ndk_dir is not None:
                info('Got NDK version from $ANDROIDNDKVER')

        self.ndk = 'google'

        try:
            with open(join(ndk_dir, 'RELEASE.TXT')) as fileh:
                reported_ndk_ver = fileh.read().split(' ')[0].strip()
        except IOError:
            pass
        else:
            if reported_ndk_ver.startswith('crystax-ndk-'):
                reported_ndk_ver = reported_ndk_ver[12:]
                self.ndk = 'crystax'
            if ndk_ver is None:
                ndk_ver = reported_ndk_ver
                info(('Got Android NDK version from the NDK dir: '
                      'it is {}').format(ndk_ver))
            else:
                if ndk_ver != reported_ndk_ver:
                    warning('NDK version was set as {}, but checking '
                            'the NDK dir claims it is {}.'.format(
                                ndk_ver, reported_ndk_ver))
                    warning('The build will try to continue, but it may '
                            'fail and you should check '
                            'that your setting is correct.')
                    warning('If the NDK dir result is correct, you don\'t '
                            'need to manually set the NDK ver.')
        if ndk_ver is None:
            warning('Android NDK version could not be found. This probably'
                    'won\'t cause any problems, but if necessary you can'
                    'set it with `--ndk-version=...`.')
        self.ndk_ver = ndk_ver

        info('Using {} NDK {}'.format(self.ndk.capitalize(), self.ndk_ver))

        virtualenv = None
        if virtualenv is None:
            virtualenv = sh.which('virtualenv2')
        if virtualenv is None:
            virtualenv = sh.which('virtualenv-2.7')
        if virtualenv is None:
            virtualenv = sh.which('virtualenv')
        if virtualenv is None:
            raise IOError('Couldn\'t find a virtualenv executable, '
                          'you must install this to use p4a.')
        self.virtualenv = virtualenv
        info('Found virtualenv at {}'.format(virtualenv))

        # path to some tools
        self.ccache = sh.which("ccache")
        if not self.ccache:
            info('ccache is missing, the build will not be optimized in the '
                 'future.')
        for cython_fn in ("cython2", "cython-2.7", "cython"):
            cython = sh.which(cython_fn)
            if cython:
                self.cython = cython
                break
        else:
            error('No cython binary found. Exiting.')
            exit(1)
        if not self.cython:
            ok = False
            warning("Missing requirement: cython is not installed")

        # This would need to be changed if supporting multiarch APKs
        arch = self.archs[0]
        platform_dir = arch.platform_dir
        include_prefix = arch.include_prefix
        toolchain_prefix = arch.toolchain_prefix
        toolchain_version = None
        self.ndk_platform = join(self.ndk_dir, 'platforms',
                                 'android-{}'.format(self.android_api),
                                 platform_dir)
        if not exists(self.ndk_platform):
            warning('ndk_platform doesn\'t exist: {}'.format(
                self.ndk_platform))
            ok = False

        py_platform = sys.platform
        if py_platform in ['linux2', 'linux3']:
            py_platform = 'linux'

        self.include_prefix = include_prefix
        include_path = join(self.ndk_dir, 'sysroot/usr/include/',
                            self.include_prefix)
        if not os.path.isdir(include_path):
            warning(
                'include directory doesn\'t exist: {}'.format(include_path))
            ok = False

        toolchain_versions = []
        toolchain_path = join(self.ndk_dir, 'toolchains')
        if os.path.isdir(toolchain_path):
            toolchain_contents = glob.glob('{}/{}-*'.format(
                toolchain_path, toolchain_prefix))
            toolchain_versions = [
                split(path)[-1][len(toolchain_prefix) + 1:]
                for path in toolchain_contents
            ]
        else:
            warning('Could not find toolchain subdirectory!')
            ok = False
        toolchain_versions.sort()

        toolchain_versions_gcc = []
        for toolchain_version in toolchain_versions:
            if toolchain_version[0].isdigit():
                # GCC toolchains begin with a number
                toolchain_versions_gcc.append(toolchain_version)

        if toolchain_versions:
            info('Found the following toolchain versions: {}'.format(
                toolchain_versions))
            info('Picking the latest gcc toolchain, here {}'.format(
                toolchain_versions_gcc[-1]))
            toolchain_version = toolchain_versions_gcc[-1]
        else:
            warning('Could not find any toolchain for {}!'.format(
                toolchain_prefix))
            ok = False

        self.toolchain_prefix = toolchain_prefix
        self.toolchain_version = toolchain_version
        # Modify the path so that sh finds modules appropriately
        environ['PATH'] = (
            '{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/'
            'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/'
            '{toolchain_prefix}-{toolchain_version}/prebuilt/'
            '{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/'
            'tools:{path}').format(sdk_dir=self.sdk_dir,
                                   ndk_dir=self.ndk_dir,
                                   toolchain_prefix=toolchain_prefix,
                                   toolchain_version=toolchain_version,
                                   py_platform=py_platform,
                                   path=environ.get('PATH'))

        for executable in ("pkg-config", "autoconf", "automake", "libtoolize",
                           "tar", "bzip2", "unzip", "make", "gcc", "g++"):
            if not sh.which(executable):
                warning("Missing executable: {} is not installed".format(
                    executable))

        if not ok:
            error('{}python-for-android cannot continue; aborting{}'.format(
                Err_Fore.RED, Err_Fore.RESET))
            sys.exit(1)
Esempio n. 47
0
    def get_env(self, with_flags_in_cc=True):
        env = {}

        env["CFLAGS"] = " ".join([
            "-DANDROID", "-mandroid", "-fomit-frame-pointer",
            "--sysroot", self.ctx.ndk_platform])

        env["CXXFLAGS"] = env["CFLAGS"]

        env["LDFLAGS"] = " ".join(['-lm', '-L' + self.ctx.get_libs_dir(self.arch)])

        if self.ctx.ndk == 'crystax':
            env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(self.ctx.ndk_dir, self.arch)

        py_platform = sys.platform
        if py_platform in ['linux2', 'linux3']:
            py_platform = 'linux'

        toolchain_prefix = self.ctx.toolchain_prefix
        toolchain_version = self.ctx.toolchain_version
        command_prefix = self.command_prefix

        env['TOOLCHAIN_PREFIX'] = toolchain_prefix
        env['TOOLCHAIN_VERSION'] = toolchain_version

        ccache = ''
        if self.ctx.ccache and bool(int(environ.get('USE_CCACHE', '1'))):
            print('ccache found, will optimize builds')
            ccache = self.ctx.ccache + ' '
            env['USE_CCACHE'] = '1'
            env['NDK_CCACHE'] = self.ctx.ccache

        print('path is', environ['PATH'])
        cc = find_executable('{command_prefix}-gcc'.format(
            command_prefix=command_prefix), path=environ['PATH'])
        if cc is None:
            warning('Couldn\'t find executable for CC. This indicates a '
                    'problem locating the {} executable in the Android '
                    'NDK, not that you don\'t have a normal compiler '
                    'installed. Exiting.')
            exit(1)

        if with_flags_in_cc:
            env['CC'] = '{ccache}{command_prefix}-gcc {cflags}'.format(
                command_prefix=command_prefix,
                ccache=ccache,
                cflags=env['CFLAGS'])
            env['CXX'] = '{ccache}{command_prefix}-g++ {cxxflags}'.format(
                command_prefix=command_prefix,
                ccache=ccache,
                cxxflags=env['CXXFLAGS'])
        else:
            env['CC'] = '{ccache}{command_prefix}-gcc'.format(
                command_prefix=command_prefix,
                ccache=ccache)
            env['CXX'] = '{ccache}{command_prefix}-g++'.format(
                command_prefix=command_prefix,
                ccache=ccache)

        env['AR'] = '{}-ar'.format(command_prefix)
        env['RANLIB'] = '{}-ranlib'.format(command_prefix)
        env['LD'] = '{}-ld'.format(command_prefix)
        # env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink')
        # env['LDSHARED'] = env['LD']
        env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
        env['MAKE'] = 'make -j5'
        env['READELF'] = '{}-readelf'.format(command_prefix)
        env['NM'] = '{}-nm'.format(command_prefix)

        hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)

        # AND: This hardcodes python version 2.7, needs fixing
        env['BUILDLIB_PATH'] = join(
            hostpython_recipe.get_build_dir(self.arch),
            'build', 'lib.linux-{}-2.7'.format(uname()[-1]))

        env['PATH'] = environ['PATH']

        env['ARCH'] = self.arch

        if self.ctx.python_recipe.from_crystax:
            env['CRYSTAX_PYTHON_VERSION'] = self.ctx.python_recipe.version

        return env
Esempio n. 48
0
    def postbuild_arch(self, arch):
        super(MobileInsightRecipe, self).postbuild_arch(arch)

        # TODO
        warning(
            'Should remove mobileinsight build tools here, skipping for now')