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_* BUILD_*
LOGS LOGS
SOURCE SOURCE
TOOLCHAINS

View File

@ -1,4 +1,4 @@
import shutil import shutil,os
from dependency_utils import ( from dependency_utils import (
Dependency, Dependency,
@ -6,9 +6,11 @@ from dependency_utils import (
GitClone, GitClone,
MakeBuilder, MakeBuilder,
CMakeBuilder, 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 # Missing dependencies
@ -318,3 +320,42 @@ class KiwixTools(Dependency):
if self.buildEnv.platform_info.static: if self.buildEnv.platform_info.static:
return "-Dstatic-linkage=true" return "-Dstatic-linkage=true"
return "" 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 context.force_native_build = True
for p in self.patches: for p in self.patches:
with open(pj(SCRIPT_DIR, 'patches', p), 'r') as patch_input: 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): def prepare(self):
self.command('download', self._download) self.command('download', self._download)
@ -305,3 +305,28 @@ class MesonBuilder(Builder):
def _install(self, context): def _install(self, context):
command = "{} -v install".format(self.buildEnv.ninja_command) command = "{} -v install".format(self.buildEnv.ninja_command)
self.buildEnv.run_command(command, self.build_path, context) 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 #!/usr/bin/env python3
import os, sys, stat import os, sys, shutil
import argparse import argparse
import ssl import ssl
import urllib.request import urllib.request
@ -13,6 +13,7 @@ from dependency_utils import ReleaseDownload, Builder
from utils import ( from utils import (
pj, pj,
remove_duplicates, remove_duplicates,
add_execution_right,
get_sha256, get_sha256,
StopBuild, StopBuild,
SkipCommand, SkipCommand,
@ -38,7 +39,7 @@ CROSS_ENV = {
} }
}, },
'fedora_android': { 'fedora_android': {
'toolchain_names': ['android_ndk'], 'toolchain_names': ['android_ndk', 'android_sdk'],
'extra_libs': [], 'extra_libs': [],
'extra_cflags': [], 'extra_cflags': [],
'host_machine': { 'host_machine': {
@ -61,7 +62,7 @@ CROSS_ENV = {
} }
}, },
'debian_android': { 'debian_android': {
'toolchain_names': ['android_ndk'], 'toolchain_names': ['android_ndk', 'android_sdk'],
'extra_libs': [], 'extra_libs': [],
'extra_cflags': [], 'extra_cflags': [],
'host_machine': { 'host_machine': {
@ -189,11 +190,13 @@ class BuildEnv:
build_dir = "BUILD_{}".format(options.target_platform) build_dir = "BUILD_{}".format(options.target_platform)
self.build_dir = pj(options.working_dir, build_dir) self.build_dir = pj(options.working_dir, build_dir)
self.archive_dir = pj(options.working_dir, "ARCHIVE") 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.log_dir = pj(self.build_dir, 'LOGS')
self.install_dir = pj(self.build_dir, "INSTALL") self.install_dir = pj(self.build_dir, "INSTALL")
for d in (self.source_dir, for d in (self.source_dir,
self.build_dir, self.build_dir,
self.archive_dir, self.archive_dir,
self.toolchain_dir,
self.log_dir, self.log_dir,
self.install_dir): self.install_dir):
os.makedirs(d, exist_ok=True) os.makedirs(d, exist_ok=True)
@ -210,6 +213,16 @@ class BuildEnv:
self.libprefix = options.libprefix or self._detect_libdir() self.libprefix = options.libprefix or self._detect_libdir()
self.targetsDict = targetsDict 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): def detect_platform(self):
_platform = platform.system() _platform = platform.system()
self.distname = _platform self.distname = _platform
@ -397,8 +410,13 @@ class BuildEnv:
kwargs = dict() kwargs = dict()
if input: if input:
kwargs['stdin'] = input kwargs['stdin'] = subprocess.PIPE
return subprocess.check_call(command, shell=True, cwd=cwd, env=env, stdout=log or sys.stdout, stderr=subprocess.STDOUT, **kwargs) 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: finally:
if log: if log:
log.close() log.close()
@ -662,8 +680,7 @@ class android_ndk(Toolchain):
def _build_platform(self, context): def _build_platform(self, context):
context.try_skip(self.build_path) context.try_skip(self.build_path)
script = pj(self.source_path, 'build/tools/make_standalone_toolchain.py') script = pj(self.source_path, 'build/tools/make_standalone_toolchain.py')
current_permissions = stat.S_IMODE(os.lstat(script).st_mode) add_execution_right(script)
os.chmod(script, current_permissions | stat.S_IXUSR)
command = '{script} --arch={arch} --api={api} --install-dir={install_dir} --force' command = '{script} --arch={arch} --api={api} --install-dir={install_dir} --force'
command = command.format( command = command.format(
script=script, script=script,
@ -687,8 +704,7 @@ class android_ndk(Toolchain):
file_path = pj(root, file_) file_path = pj(root, file_)
if os.path.islink(file_path): if os.path.islink(file_path):
continue continue
current_permissions = stat.S_IMODE(os.lstat(file_path).st_mode) add_execution_right(file_path)
os.chmod(file_path, current_permissions | stat.S_IXUSR)
def build(self): def build(self):
self.command('build_platform', self._build_platform) self.command('build_platform', self._build_platform)
@ -713,6 +729,56 @@ class android_ndk(Toolchain):
env['NDK_DEBUG'] = '0' 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: class Builder:
def __init__(self, options): def __init__(self, options):
self.options = options self.options = options
@ -781,6 +847,12 @@ class Builder:
self.prepare_sources() self.prepare_sources()
print("[BUILD]") print("[BUILD]")
self.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: except StopBuild:
sys.exit("Stopping build due to errors") sys.exit("Stopping build due to errors")
@ -801,6 +873,8 @@ def parse_args():
help="Skip the source download part") help="Skip the source download part")
parser.add_argument('--build-deps-only', action='store_true', parser.add_argument('--build-deps-only', action='store_true',
help=("Build only the dependencies of the specified targets.")) 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() return parser.parse_args()

View File

@ -14,7 +14,7 @@ if [[ "$TRAVIS_EVENT_TYPE" = "cron" ]]
then then
if [[ ${PLATFORM} = android* ]] if [[ ${PLATFORM} = android* ]]
then then
TARGETS="libzim kiwix-lib" TARGETS="libzim kiwix-lib kiwix-android"
else else
TARGETS="libzim kiwix-lib kiwix-tools" TARGETS="libzim kiwix-lib kiwix-tools"
fi fi
@ -71,13 +71,18 @@ EOF
tar -czf "${NIGHTLY_ARCHIVES_DIR}/$ARCHIVE_NAME" $FILES_LIST 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 esac
else else
# No a cron job, we just have to build to be sure nothing is broken. # No a cron job, we just have to build to be sure nothing is broken.
if [[ ${PLATFORM} = android* ]] if [[ ${PLATFORM} = android* ]]
then then
TARGET=kiwix-lib TARGET=kiwix-android
else else
TARGET=kiwix-tools TARGET=kiwix-tools
fi fi

View File

@ -2,7 +2,8 @@ import os.path
import hashlib import hashlib
import tarfile, zipfile import tarfile, zipfile
import tempfile import tempfile
import os import shutil
import os, stat
from collections import namedtuple, defaultdict from collections import namedtuple, defaultdict
pj = os.path.join pj = os.path.join
@ -32,6 +33,22 @@ def get_sha256(path):
return sha256.hexdigest() 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): class SkipCommand(Exception):
pass pass