Merge pull request #20 from kiwix/android_apk

Android apk
This commit is contained in:
Matthieu Gautier 2017-04-25 10:04:28 +02:00 committed by GitHub
commit 2f1a7bff77
6 changed files with 179 additions and 16 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ ARCHIVE
BUILD_*
LOGS
SOURCE
TOOLCHAINS

View File

@ -1,4 +1,4 @@
import shutil
import shutil,os
from dependency_utils import (
Dependency,
@ -6,9 +6,11 @@ from dependency_utils import (
GitClone,
MakeBuilder,
CMakeBuilder,
MesonBuilder)
MesonBuilder,
GradleBuilder,
Builder as BaseBuilder)
from utils import Remotefile, pj, SkipCommand
from utils import Remotefile, pj, SkipCommand, copy_tree, add_execution_right
# *************************************
# Missing dependencies
@ -318,3 +320,42 @@ class KiwixTools(Dependency):
if self.buildEnv.platform_info.static:
return "-Dstatic-linkage=true"
return ""
class Gradle(Dependency):
name = "Gradle"
version = "3.4"
class Source(ReleaseDownload):
archive = Remotefile('gradle-3.4-bin.zip',
'72d0cd4dcdd5e3be165eb7cd7bbd25cf8968baf400323d9ab1bba622c3f72205',
'https://services.gradle.org/distributions/gradle-3.4-bin.zip')
class Builder(BaseBuilder):
def build(self):
self.command('install', self._install)
def _install(self, context):
copy_tree(
pj(self.source_path, "bin"),
pj(self.buildEnv.install_dir, "bin"),
post_copy_function = add_execution_right)
copy_tree(
pj(self.source_path, "lib"),
pj(self.buildEnv.install_dir, "lib"))
class KiwixAndroid(Dependency):
name = "kiwix-android"
dependencies = ["Gradle", "kiwix-lib"]
class Source(GitClone):
git_remote = "https://github.com/kiwix/kiwix-android"
git_dir = "kiwix-android"
class Builder(GradleBuilder):
def _configure(self, context):
if not os.path.exists(self.build_path):
shutil.copytree(self.source_path, self.build_path)
shutil.rmtree(pj(self.build_path, 'kiwixlib', 'src', 'main'))
shutil.copytree(pj(self.buildEnv.install_dir, 'kiwix-lib'), pj(self.build_path, 'kiwixlib', 'src', 'main'))

View File

@ -110,7 +110,7 @@ class ReleaseDownload(Source):
context.force_native_build = True
for p in self.patches:
with open(pj(SCRIPT_DIR, 'patches', p), 'r') as patch_input:
self.buildEnv.run_command("patch -p1", self.extract_path, context, input=patch_input)
self.buildEnv.run_command("patch -p1", self.extract_path, context, input=patch_input.read())
def prepare(self):
self.command('download', self._download)
@ -305,3 +305,28 @@ class MesonBuilder(Builder):
def _install(self, context):
command = "{} -v install".format(self.buildEnv.ninja_command)
self.buildEnv.run_command(command, self.build_path, context)
class GradleBuilder(Builder):
def build(self):
self.command('configure', self._configure)
if hasattr(self, '_pre_compile_script'):
self.command('pre_compile_script', self._pre_compile_script)
self.command('compile', self._compile)
self.command('install', self._install)
def _configure(self, context):
# We don't have a lot to configure by itself
context.try_skip(self.build_path)
if os.path.exists(self.build_path):
shutil.rmtree(self.build_path)
shutil.copytree(self.source_path, self.build_path)
def _compile(self, context):
command = "gradle clean assemble --info"
self.buildEnv.run_command(command, self.build_path, context)
command = "gradle build --info"
self.buildEnv.run_command(command, self.build_path, context)
def _install(self, context):
pass

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import os, sys, stat
import os, sys, shutil
import argparse
import ssl
import urllib.request
@ -13,6 +13,7 @@ from dependency_utils import ReleaseDownload, Builder
from utils import (
pj,
remove_duplicates,
add_execution_right,
get_sha256,
StopBuild,
SkipCommand,
@ -38,7 +39,7 @@ CROSS_ENV = {
}
},
'fedora_android': {
'toolchain_names': ['android_ndk'],
'toolchain_names': ['android_ndk', 'android_sdk'],
'extra_libs': [],
'extra_cflags': [],
'host_machine': {
@ -61,7 +62,7 @@ CROSS_ENV = {
}
},
'debian_android': {
'toolchain_names': ['android_ndk'],
'toolchain_names': ['android_ndk', 'android_sdk'],
'extra_libs': [],
'extra_cflags': [],
'host_machine': {
@ -189,11 +190,13 @@ class BuildEnv:
build_dir = "BUILD_{}".format(options.target_platform)
self.build_dir = pj(options.working_dir, build_dir)
self.archive_dir = pj(options.working_dir, "ARCHIVE")
self.toolchain_dir = pj(options.working_dir, "TOOLCHAINS")
self.log_dir = pj(self.build_dir, 'LOGS')
self.install_dir = pj(self.build_dir, "INSTALL")
for d in (self.source_dir,
self.build_dir,
self.archive_dir,
self.toolchain_dir,
self.log_dir,
self.install_dir):
os.makedirs(d, exist_ok=True)
@ -210,6 +213,16 @@ class BuildEnv:
self.libprefix = options.libprefix or self._detect_libdir()
self.targetsDict = targetsDict
def clean_intermediate_directories(self):
for subdir in os.listdir(self.build_dir):
subpath = pj(self.build_dir, subdir)
if subpath == self.install_dir:
continue
if os.path.isdir(subpath):
shutil.rmtree(subpath)
else:
os.remove(subpath)
def detect_platform(self):
_platform = platform.system()
self.distname = _platform
@ -397,8 +410,13 @@ class BuildEnv:
kwargs = dict()
if input:
kwargs['stdin'] = input
return subprocess.check_call(command, shell=True, cwd=cwd, env=env, stdout=log or sys.stdout, stderr=subprocess.STDOUT, **kwargs)
kwargs['stdin'] = subprocess.PIPE
process = subprocess.Popen(command, shell=True, cwd=cwd, env=env, stdout=log or sys.stdout, stderr=subprocess.STDOUT, **kwargs)
if input:
process.communicate(input.encode())
retcode = process.wait()
if retcode:
raise subprocess.CalledProcessError(retcode, command)
finally:
if log:
log.close()
@ -662,8 +680,7 @@ class android_ndk(Toolchain):
def _build_platform(self, context):
context.try_skip(self.build_path)
script = pj(self.source_path, 'build/tools/make_standalone_toolchain.py')
current_permissions = stat.S_IMODE(os.lstat(script).st_mode)
os.chmod(script, current_permissions | stat.S_IXUSR)
add_execution_right(script)
command = '{script} --arch={arch} --api={api} --install-dir={install_dir} --force'
command = command.format(
script=script,
@ -687,8 +704,7 @@ class android_ndk(Toolchain):
file_path = pj(root, file_)
if os.path.islink(file_path):
continue
current_permissions = stat.S_IMODE(os.lstat(file_path).st_mode)
os.chmod(file_path, current_permissions | stat.S_IXUSR)
add_execution_right(file_path)
def build(self):
self.command('build_platform', self._build_platform)
@ -713,6 +729,56 @@ class android_ndk(Toolchain):
env['NDK_DEBUG'] = '0'
class android_sdk(Toolchain):
name = 'android-sdk'
version = 'r25.2.3'
class Source(ReleaseDownload):
archive = Remotefile('tools_r25.2.3-linux.zip',
'1b35bcb94e9a686dff6460c8bca903aa0281c6696001067f34ec00093145b560',
'https://dl.google.com/android/repository/tools_r25.2.3-linux.zip')
class Builder(Builder):
@property
def install_path(self):
return pj(self.buildEnv.toolchain_dir, self.target.full_name)
def _build_platform(self, context):
context.try_skip(self.install_path)
tools_dir = pj(self.install_path, 'tools')
shutil.copytree(self.source_path, tools_dir)
script = pj(tools_dir, 'android')
command = '{script} --verbose update sdk -a --no-ui --filter {packages}'
command = command.format(
script=script,
packages = ','.join(str(i) for i in [1,2,8,34,162])
)
# packages correspond to :
# - 1 : Android SDK Tools, revision 25.2.5
# - 2 : Android SDK Platform-tools, revision 25.0.3
# - 8 : Android SDK Build-tools, revision 24.0.1
# - 34 : SDK Platform Android 7.0, API 24, revision 2
# - 162 : Android Support Repository, revision 44
self.buildEnv.run_command(command, self.install_path, context, input="y\n")
def _fix_licenses(self, context):
context.try_skip(self.install_path)
os.makedirs(pj(self.install_path, 'licenses'), exist_ok=True)
with open(pj(self.install_path, 'licenses', 'android-sdk-license'), 'w') as f:
f.write("\n8933bad161af4178b1185d1a37fbf41ea5269c55")
def build(self):
self.command('build_platform', self._build_platform)
self.command('fix_licenses', self._fix_licenses)
def get_bin_dir(self):
return []
def set_env(self, env):
env['ANDROID_HOME'] = self.builder.install_path
class Builder:
def __init__(self, options):
self.options = options
@ -781,6 +847,12 @@ class Builder:
self.prepare_sources()
print("[BUILD]")
self.build()
# No error, clean intermediate file at end of build if needed.
print("[CLEAN]")
if self.buildEnv.options.clean_at_end:
self.buildEnv.clean_intermediate_directories()
else:
print("SKIP")
except StopBuild:
sys.exit("Stopping build due to errors")
@ -801,6 +873,8 @@ def parse_args():
help="Skip the source download part")
parser.add_argument('--build-deps-only', action='store_true',
help=("Build only the dependencies of the specified targets."))
parser.add_argument('--clean-at-end', action='store_true',
help="Clean all intermediate files after the (successfull) build")
return parser.parse_args()

View File

@ -14,7 +14,7 @@ if [[ "$TRAVIS_EVENT_TYPE" = "cron" ]]
then
if [[ ${PLATFORM} = android* ]]
then
TARGETS="libzim kiwix-lib"
TARGETS="libzim kiwix-lib kiwix-android"
else
TARGETS="libzim kiwix-lib kiwix-tools"
fi
@ -71,13 +71,18 @@ EOF
tar -czf "${NIGHTLY_ARCHIVES_DIR}/$ARCHIVE_NAME" $FILES_LIST
)
;;
android_*)
APK_NAME="kiwix-${PLATFORM}"
cp ${BASE_DIR}/kiwix-android/app/build/outputs/apk/app-kiwix-debug.apk ${NIGHTLY_ARCHIVES_DIR}/${APK_NAME}-debug.apk
cp ${BASE_DIR}/kiwix-android/app/build/outputs/apk/app-kiwix-release-unsigned.apk ${NIGHTLY_ARCHIVES_DIR}/${APK_NAME}-release-unsigned.apk
;;
esac
else
# No a cron job, we just have to build to be sure nothing is broken.
if [[ ${PLATFORM} = android* ]]
then
TARGET=kiwix-lib
TARGET=kiwix-android
else
TARGET=kiwix-tools
fi

View File

@ -2,7 +2,8 @@ import os.path
import hashlib
import tarfile, zipfile
import tempfile
import os
import shutil
import os, stat
from collections import namedtuple, defaultdict
pj = os.path.join
@ -32,6 +33,22 @@ def get_sha256(path):
return sha256.hexdigest()
def add_execution_right(file_path):
current_permissions = stat.S_IMODE(os.lstat(file_path).st_mode)
os.chmod(file_path, current_permissions | stat.S_IXUSR)
def copy_tree(src, dst, post_copy_function=None):
os.makedirs(dst, exist_ok=True)
for root, dirs, files in os.walk(src):
r = os.path.relpath(root, src)
dstdir = pj(dst, r)
os.makedirs(dstdir, exist_ok=True)
for f in files:
dstfile = pj(dstdir, f)
shutil.copy2(pj(root, f), dstfile)
if post_copy_function is not None:
post_copy_function(dstfile)
class SkipCommand(Exception):
pass