Browse Source
- added TrustWord DB in assets - added GPG external build based on gnupg-for-android external build - added runtime asset unpacking into file system, for native access - added gradle tasks and dependencies to invoke external and pEpEngine build - added GPGAgentService and associated android intent to start gpg-agent - added dumb pinentry.sh to keep gpg-agent happy Note : temporary added import_key to basic_api for testing purposeJNI-44

15 changed files with 1045 additions and 18 deletions
@ -0,0 +1,527 @@ |
|||||
|
#------------------------------------------------------------------------------#
|
||||
|
# Makefile to build GPGME, GnuPG and deps for use with pEpEngine
|
||||
|
# based on gnupg-for-android/external/Makefile
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# Build parameters
|
||||
|
|
||||
|
# TODO: get params from the outside for multiarch build
|
||||
|
|
||||
|
NDK_ABI ?= arm |
||||
|
NDK_TOOLCHAIN_VERSION ?= 4.8 |
||||
|
APP_ABI ?= armeabi-v7a |
||||
|
APP_PLATFORM ?= android-14 |
||||
|
PEP_PACKAGE_NAME ?= com.pep.pepjniaaractivity |
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# Clone update and archive external projects GIT repos
|
||||
|
# Local clone is in external/$project.git while
|
||||
|
# slected commit is archived in external/$project
|
||||
|
|
||||
|
EXTERNAL_GIT_REPOS = \
|
||||
|
gnupg|git://git.gnupg.org/gnupg.git?08c00cd\ |
||||
|
libassuan|git://git.gnupg.org/libassuan.git?261498d\ |
||||
|
libgcrypt|git://git.gnupg.org/libgcrypt.git?b3936b6\ |
||||
|
libgpg-error|git://git.gnupg.org/libgpg-error.git?bcd9295\ |
||||
|
libksba|git://git.gnupg.org/libksba.git?02079b5\ |
||||
|
curl|https://github.com/bagder/curl?f77e89c\ |
||||
|
npth|git://git.gnupg.org/npth.git?79fbdce\ |
||||
|
gpgme|git://git.gnupg.org/gpgme.git?37d927a |
||||
|
|
||||
|
define per_repo_targets |
||||
|
$(1).git: |
||||
|
git clone $(2) $(1).git |
||||
|
|
||||
|
$(1).git_update: $(1).git |
||||
|
cd $(1).git; git pull |
||||
|
|
||||
|
$(1): |$(1).git |
||||
|
mkdir $(1) |
||||
|
(cd $(1).git; git archive --format=tar $(3)) | tar -C $(1) -x |
||||
|
|
||||
|
$(1)_clean: |
||||
|
rm -rf $(1) |
||||
|
|
||||
|
EXTERNAL_LOCAL_GITS += $(1).git |
||||
|
EXTERNAL_LOCAL_GITS_UPDATE += $(1).git_update |
||||
|
EXTERNAL_SRCS += $(1) |
||||
|
EXTERNAL_SRCS_CLEAN += $(1)_clean |
||||
|
endef |
||||
|
|
||||
|
define per_repo |
||||
|
$(call per_repo_targets,\ |
||||
|
$(1),\
|
||||
|
$(word 1,$(subst ?, ,$(2))),\
|
||||
|
$(word 2,$(subst ?, ,$(2)))) |
||||
|
endef |
||||
|
|
||||
|
$(foreach repo, $(EXTERNAL_GIT_REPOS), $(eval $(call per_repo,\ |
||||
|
$(word 1,$(subst |, ,$(repo))),\
|
||||
|
$(word 2,$(subst |, ,$(repo)))))) |
||||
|
|
||||
|
git_update: $(EXTERNAL_LOCAL_GITS_UPDATE) |
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# Manage paths for PREFIX, DESTDIR, LOCAL and PATH
|
||||
|
|
||||
|
EXTERNAL_ROOT := $(shell pwd) |
||||
|
|
||||
|
# install root for built files
|
||||
|
DESTDIR = $(EXTERNAL_ROOT) |
||||
|
prefix = /data/data/$(PEP_PACKAGE_NAME)/app_opt |
||||
|
LOCAL := $(DESTDIR)$(prefix) |
||||
|
|
||||
|
PATH := ${PATH}:$(NDK_TOOLCHAIN)/bin:$(LOCAL)/bin |
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# NDK toolchain integration
|
||||
|
# TODO: cleanup.
|
||||
|
|
||||
|
# Android now has 64-bit and 32-bit versions of the NDK for GNU/Linux. We
|
||||
|
# assume that the build platform uses the appropriate version, otherwise the
|
||||
|
# user building this will have to manually set NDK_PROCESSOR or NDK_TOOLCHAIN.
|
||||
|
CPU := $(shell uname -m) |
||||
|
ifeq ($(CPU),x86_64) |
||||
|
NDK_PROCESSOR=x86_64 |
||||
|
else |
||||
|
NDK_PROCESSOR=x86 |
||||
|
endif |
||||
|
|
||||
|
NDK_SYSROOT=$(ANDROID_NDK_HOME)/platforms/$(APP_PLATFORM)/arch-$(NDK_ABI) |
||||
|
NDK_UNAME := $(shell uname -s | tr '[A-Z]' '[a-z]') |
||||
|
ifeq ($(NDK_ABI),x86) |
||||
|
HOST = i686-linux-android |
||||
|
NDK_TOOLCHAIN = $(NDK_ABI)-$(NDK_TOOLCHAIN_VERSION) |
||||
|
else |
||||
|
HOST = $(NDK_ABI)-linux-androideabi |
||||
|
NDK_TOOLCHAIN = $(HOST)-$(NDK_TOOLCHAIN_VERSION) |
||||
|
endif |
||||
|
NDK_TOOLCHAIN_BASE=$(ANDROID_NDK_HOME)/toolchains/$(NDK_TOOLCHAIN)/prebuilt/$(NDK_UNAME)-$(NDK_PROCESSOR) |
||||
|
|
||||
|
# include Android's build flags
|
||||
|
TARGET_ARCH_ABI = $(APP_ABI) |
||||
|
include $(ANDROID_NDK_HOME)/toolchains/$(NDK_TOOLCHAIN)/setup.mk |
||||
|
|
||||
|
CC := $(NDK_TOOLCHAIN_BASE)/bin/$(HOST)-gcc --sysroot=$(NDK_SYSROOT) |
||||
|
CXX := $(NDK_TOOLCHAIN_BASE)/bin/$(HOST)-g++ |
||||
|
CPP := $(NDK_TOOLCHAIN_BASE)/bin/$(HOST)-cpp |
||||
|
LD := $(NDK_TOOLCHAIN_BASE)/bin/$(HOST)-ld |
||||
|
AR := $(NDK_TOOLCHAIN_BASE)/bin/$(HOST)-ar |
||||
|
RANLIB := $(NDK_TOOLCHAIN_BASE)/bin/$(HOST)-ranlib |
||||
|
STRIP := $(NDK_TOOLCHAIN_BASE)/bin/$(HOST)-strip \
|
||||
|
--strip-unneeded -R .note -R .comment |
||||
|
|
||||
|
CFLAGS = -DANDROID -I$(LOCAL)/include $(TARGET_CFLAGS) |
||||
|
LDFLAGS = -llog -L$(LOCAL)/lib $(TARGET_LDFLAGS) |
||||
|
|
||||
|
# change 'release' to 'debug' for unoptimized debug builds
|
||||
|
ifeq ($(APP_ABI),armeabi-v7a) |
||||
|
CFLAGS += $(TARGET_arm_release_CFLAGS) |
||||
|
endif |
||||
|
ifeq ($(APP_ABI),armeabi) |
||||
|
CFLAGS += $(TARGET_thumb_release_CFLAGS) |
||||
|
endif |
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# GNU Tools trickery
|
||||
|
|
||||
|
# point pkg-config to the .pc files generated from these builds
|
||||
|
export PKG_CONFIG_PATH=$(LOCAL)/lib/pkgconfig |
||||
|
# workaround for cross-compiling bug in autoconf
|
||||
|
export ac_cv_func_malloc_0_nonnull=yes |
||||
|
|
||||
|
# GnuPG does not work with automake 1.14, it has proven reliable on 1.11, and
|
||||
|
# that's widely available, so we'll add it to the env here, and the
|
||||
|
# ./autogen.sh scripts will pick it up.
|
||||
|
# http://lists.gnupg.org/pipermail/gnupg-devel/2013-August/027857.html
|
||||
|
export AUTOMAKE=automake-1.11 |
||||
|
|
||||
|
|
||||
|
.PHONY = clean distclean install-clean \
|
||||
|
libgpg-error-build libgpg-error-clean libgpg-error-install \
|
||||
|
libgcrypt-build libgcrypt-clean libgcrypt-install \
|
||||
|
libassuan-build libassuan-clean libassuan-install \
|
||||
|
libksba-build libksba-clean libksba-install \
|
||||
|
gnupg-build gnupg-clean \
|
||||
|
curl-build curl-clean curl-install \
|
||||
|
npth-build npth-clean npth-install \
|
||||
|
assets clean-assets \
|
||||
|
$(EXTERNAL_LOCAL_GITS_UPDATE) $(EXTERNAL_SRCS_CLEAN) \
|
||||
|
showsetup |
||||
|
|
||||
|
all: gnupg-install gpgme-install assets |
||||
|
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# debugging stuff
|
||||
|
|
||||
|
showsetup: |
||||
|
@echo "NDK_TOOLCHAIN_VERSION: $(NDK_TOOLCHAIN_VERSION)" |
||||
|
@echo "NDK_TOOLCHAIN: $(NDK_TOOLCHAIN)" |
||||
|
@echo "NDK_SYSROOT: $(NDK_SYSROOT)" |
||||
|
@echo "APP_PLATFORM: $(APP_PLATFORM)" |
||||
|
@echo "APP_ABI: $(APP_ABI)" |
||||
|
@echo "HOST: $(HOST)" |
||||
|
@echo "CC: $(CC)" |
||||
|
@echo "LD: $(LD)" |
||||
|
@echo "CFLAGS: $(CFLAGS)" |
||||
|
@echo "LDFLAGS: $(LDFLAGS)" |
||||
|
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# libgpg-error
|
||||
|
|
||||
|
libgpg-error/configure: libgpg-error libgpg-error/configure.ac |
||||
|
cd libgpg-error && ./autogen.sh |
||||
|
|
||||
|
libgpg-error/Makefile: libgpg-error/configure |
||||
|
cd libgpg-error && \
|
||||
|
./configure \
|
||||
|
CC="$(CC)" \
|
||||
|
AR=$(AR) \
|
||||
|
RANLIB=$(RANLIB) \
|
||||
|
CFLAGS="$(CFLAGS)" \
|
||||
|
LDFLAGS="$(LDFLAGS)" \
|
||||
|
--disable-doc \
|
||||
|
--disable-languages \
|
||||
|
--host=$(HOST) \
|
||||
|
--prefix=$(LOCAL) |
||||
|
ls -l libgpg-error/libtool |
||||
|
# brute force and ignorance to make libtool comply with android style |
||||
|
sed -i 's,^fast_install=.*,fast_install=needless,' libgpg-error/libtool |
||||
|
sed -i 's,^version_type=.*,version_type=none,' libgpg-error/libtool |
||||
|
sed -i 's,^shlibpath_overrides_runpath=.*,shlibpath_overrides_runpath=yes,' libgpg-error/libtool |
||||
|
sed -i 's,^library_names_spec=.*,library_names_spec="\\$$libname\\$$release\\$$shared_ext",' libgpg-error/libtool |
||||
|
sed -i 's,^soname_spec=.*,soname_spec="\\$$libname\\$$release\\$$shared_ext",' libgpg-error/libtool |
||||
|
sed -i 's,^finish_cmds=.*,finish_cmds="",' libgpg-error/libtool |
||||
|
sed -i 's,^sys_lib_dlsearch_path_spec=.*,sys_lib_dlsearch_path_spec="/lib /usr/lib",' libgpg-error/libtool |
||||
|
|
||||
|
libgpg-error/src/.libs/libgpg-error.so: libgpg-error/Makefile |
||||
|
$(MAKE) -C libgpg-error |
||||
|
|
||||
|
libgpg-error-build: libgpg-error/src/.libs/libgpg-error.so |
||||
|
|
||||
|
$(LOCAL)/lib/libgpg-error.so: libgpg-error/src/.libs/libgpg-error.so |
||||
|
$(MAKE) -C libgpg-error prefix=$(LOCAL) install |
||||
|
ls -l $(LOCAL)/lib/libgpg-error.so* |
||||
|
|
||||
|
libgpg-error-install: $(LOCAL)/lib/libgpg-error.so |
||||
|
|
||||
|
libgpg-error-clean: |
||||
|
-$(MAKE) -C libgpg-error clean |
||||
|
rm -rf libgpg-error/configure libgpg-error/Makefile |
||||
|
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# libgcrypt
|
||||
|
|
||||
|
libgcrypt/configure: libgcrypt libgcrypt/configure.ac |
||||
|
cd libgcrypt && ./autogen.sh |
||||
|
|
||||
|
libgcrypt/Makefile: libgcrypt/configure |
||||
|
cd libgcrypt && \
|
||||
|
CC="$(CC)" AR="$(AR)" RANLIB=$(RANLIB) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
||||
|
./configure \
|
||||
|
--enable-maintainer-mode \
|
||||
|
--host=$(HOST) \
|
||||
|
--with-gpg-error-prefix=$(LOCAL) \
|
||||
|
--prefix=$(LOCAL) |
||||
|
-patch -N -p1 --reject-file=- libgcrypt/tests/random.c libgcrypt-disable-hanging-random-test.patch |
||||
|
|
||||
|
libgcrypt/src/.libs/libgcrypt.so: $(LOCAL)/lib/libgpg-error.so libgcrypt/Makefile |
||||
|
$(MAKE) -C libgcrypt |
||||
|
|
||||
|
$(LOCAL)/lib/libgcrypt.so: libgcrypt/src/.libs/libgcrypt.so |
||||
|
$(MAKE) -C libgcrypt prefix=$(LOCAL) install |
||||
|
ls -l $(LOCAL)/lib/libgcrypt.so |
||||
|
|
||||
|
libgcrypt-build: libgcrypt/src/.libs/libgcrypt.so |
||||
|
|
||||
|
libgcrypt-install: $(LOCAL)/lib/libgcrypt.so |
||||
|
|
||||
|
libgcrypt-clean: |
||||
|
-$(MAKE) -C libgcrypt clean |
||||
|
rm -rf libgcrypt/configure libgcrypt/Makefile |
||||
|
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# libassuan
|
||||
|
|
||||
|
libassuan/configure: libassuan libassuan/configure.ac |
||||
|
cd libassuan && ./autogen.sh && autoreconf --install --force --verbose |
||||
|
|
||||
|
libassuan/Makefile: libassuan/configure |
||||
|
-patch -N -p1 --reject-file=- libassuan/m4/libtool.m4 libtool-Add-Android-Linux-support.patch |
||||
|
cd libassuan && \
|
||||
|
CC="$(CC)" AR="$(AR)" RANLIB=$(RANLIB) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
||||
|
./configure \
|
||||
|
--enable-maintainer-mode \
|
||||
|
--host=$(HOST) \
|
||||
|
--with-gpg-error-prefix=$(LOCAL) \
|
||||
|
--prefix=$(LOCAL) |
||||
|
|
||||
|
libassuan/src/.libs/libassuan.so: $(LOCAL)/lib/libgpg-error.so libassuan/Makefile |
||||
|
$(MAKE) -C libassuan |
||||
|
|
||||
|
$(LOCAL)/lib/libassuan.so: libassuan/src/.libs/libassuan.so |
||||
|
$(MAKE) -C libassuan prefix=$(LOCAL) install |
||||
|
ls -l $(LOCAL)/lib/libassuan.so |
||||
|
|
||||
|
libassuan-build: libassuan/src/.libs/libassuan.so |
||||
|
|
||||
|
libassuan-install: $(LOCAL)/lib/libassuan.so |
||||
|
|
||||
|
libassuan-clean: |
||||
|
-$(MAKE) -C libassuan clean |
||||
|
rm -rf libassuan/configure libassuan/Makefile |
||||
|
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# npth
|
||||
|
|
||||
|
npth/configure: npth npth/configure.ac |
||||
|
cd npth && ./autogen.sh && autoreconf --install --force --verbose |
||||
|
|
||||
|
npth/Makefile: npth/configure |
||||
|
-patch -N -p1 --reject-file=- npth/m4/libtool.m4 libtool-Add-Android-Linux-support.patch |
||||
|
cd npth && \
|
||||
|
CC="$(CC)" AR="$(AR)" RANLIB=$(RANLIB) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
||||
|
./configure \
|
||||
|
--enable-maintainer-mode \
|
||||
|
--host=$(HOST) \
|
||||
|
--with-gnu-ld \
|
||||
|
--prefix=$(LOCAL) |
||||
|
|
||||
|
npth/src/.libs/libnpth.so: $(LOCAL)/lib/libgpg-error.so npth/Makefile |
||||
|
$(MAKE) -C npth |
||||
|
|
||||
|
$(LOCAL)/lib/libnpth.so: npth/src/.libs/libnpth.so |
||||
|
$(MAKE) -C npth prefix=$(LOCAL) install |
||||
|
ls -l $(LOCAL)/lib/libnpth.so* |
||||
|
|
||||
|
npth-build: npth/src/.libs/libnpth.so |
||||
|
|
||||
|
npth-install: $(LOCAL)/lib/libnpth.so |
||||
|
|
||||
|
npth-clean: |
||||
|
-$(MAKE) -C npth clean |
||||
|
rm -rf npth/Makefile npth/configure |
||||
|
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# libksba
|
||||
|
|
||||
|
libksba/configure: libksba libksba/configure.ac |
||||
|
cd libksba && ./autogen.sh |
||||
|
|
||||
|
libksba/Makefile: $(LOCAL)/lib/libgpg-error.so libksba/configure |
||||
|
-patch -N -p1 --reject-file=- libksba/m4/libtool.m4 libtool-Add-Android-Linux-support.patch |
||||
|
cd libksba && \
|
||||
|
CC="$(CC)" AR="$(AR)" RANLIB=$(RANLIB) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
||||
|
./configure \
|
||||
|
--enable-maintainer-mode \
|
||||
|
--host=$(HOST) \
|
||||
|
--with-gpg-error-prefix=$(LOCAL) \
|
||||
|
--prefix=$(LOCAL) |
||||
|
|
||||
|
libksba/src/.libs/libksba.so: libksba/Makefile |
||||
|
$(MAKE) -C libksba |
||||
|
|
||||
|
$(LOCAL)/lib/libksba.so: libksba/src/.libs/libksba.so |
||||
|
$(MAKE) -C libksba prefix=$(LOCAL) install |
||||
|
ls -l $(LOCAL)/lib/libksba.so |
||||
|
|
||||
|
libksba-build: libksba/src/.libs/libksba.so |
||||
|
|
||||
|
libksba-install: $(LOCAL)/lib/libksba.so |
||||
|
|
||||
|
libksba-clean: |
||||
|
-$(MAKE) -C libksba clean |
||||
|
rm -rf libksba/configure libksba/Makefile |
||||
|
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# curl
|
||||
|
|
||||
|
curl/configure: curl curl/configure.ac |
||||
|
cd curl && ./buildconf |
||||
|
|
||||
|
curl/Makefile: curl/configure |
||||
|
-patch -N -p1 --reject-file=- curl/m4/libtool.m4 libtool-Add-Android-Linux-support.patch |
||||
|
cd curl && \
|
||||
|
CC="$(CC)" AR="$(AR)" RANLIB=$(RANLIB) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
||||
|
./configure \
|
||||
|
--host=$(HOST) \
|
||||
|
--prefix=$(prefix) \
|
||||
|
--with-gnu-ld \
|
||||
|
--disable-imap \
|
||||
|
--disable-ldap \
|
||||
|
--disable-pop3 \
|
||||
|
--disable-rtsp \
|
||||
|
--disable-smtp |
||||
|
# brute force and ignorance to make libtool comply with android style |
||||
|
sed -i 's,^fast_install=.*,fast_install=needless,' curl/libtool |
||||
|
sed -i 's,^version_type=.*,version_type=none,' curl/libtool |
||||
|
sed -i 's,^shlibpath_overrides_runpath=.*,shlibpath_overrides_runpath=yes,' curl/libtool |
||||
|
sed -i 's,^library_names_spec=.*,library_names_spec="\\$$libname\\$$release\\$$shared_ext",' curl/libtool |
||||
|
sed -i 's,^soname_spec=.*,soname_spec="\\$$libname\\$$release\\$$shared_ext",' curl/libtool |
||||
|
sed -i 's,^finish_cmds=.*,finish_cmds="",' curl/libtool |
||||
|
sed -i 's,^sys_lib_dlsearch_path_spec=.*,sys_lib_dlsearch_path_spec="/lib /usr/lib",' curl/libtool |
||||
|
|
||||
|
curl/lib/.libs/libcurl.so: curl/Makefile |
||||
|
$(MAKE) -C curl |
||||
|
|
||||
|
$(LOCAL)/lib/libcurl.so: curl/lib/.libs/libcurl.so |
||||
|
$(MAKE) -C curl DESTDIR=$(DESTDIR) prefix=$(prefix) install |
||||
|
ls -l $(LOCAL)/lib/libcurl.so |
||||
|
|
||||
|
curl-build: curl/lib/.libs/libcurl.so |
||||
|
|
||||
|
curl-install: $(LOCAL)/lib/libcurl.so |
||||
|
|
||||
|
curl-clean: |
||||
|
-$(MAKE) -C curl clean |
||||
|
rm -f curl/Makefile |
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# gnupg
|
||||
|
|
||||
|
gnupg/configure: gnupg |
||||
|
cd gnupg && ./autogen.sh |
||||
|
|
||||
|
gnupg/Makefile: gnupg/configure |
||||
|
cd gnupg && \
|
||||
|
CC="$(CC)" AR="$(AR)" RANLIB=$(RANLIB) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
||||
|
./configure \
|
||||
|
--enable-maintainer-mode \
|
||||
|
--host=$(HOST) \
|
||||
|
--with-gpg-error-prefix=$(LOCAL) \
|
||||
|
--with-libgcrypt-prefix=$(LOCAL) \
|
||||
|
--with-libassuan-prefix=$(LOCAL) \
|
||||
|
--with-ksba-prefix=$(LOCAL) \
|
||||
|
--with-npth-prefix=$(LOCAL) \
|
||||
|
--with-libcurl=$(LOCAL) \
|
||||
|
--disable-ldap \
|
||||
|
--without-libiconv-prefix \
|
||||
|
--disable-doc \
|
||||
|
--disable-g13 \
|
||||
|
--disable-ntbtls \
|
||||
|
--disable-gnutls \
|
||||
|
--enable-dirmngr-auto-start \
|
||||
|
--with-agent-pgm=$(prefix)/bin/gpg-agent \
|
||||
|
--with-pinentry-pgm=$(prefix)/bin/pinentry.sh \
|
||||
|
--with-dirmngr-pgm=$(prefix)/bin/dirmngr \
|
||||
|
--with-protect-tool-pgm=$(prefix)/libexec/gpg-protect-tool \
|
||||
|
--with-scdaemon-pgm=$(prefix)/bin/scdaemon \
|
||||
|
--prefix=$(prefix) |
||||
|
|
||||
|
gnupg/g10/gpg2: $(LOCAL)/lib/libgpg-error.so $(LOCAL)/lib/libgcrypt.so $(LOCAL)/lib/libksba.so $(LOCAL)/lib/libassuan.so $(LOCAL)/lib/libnpth.so $(LOCAL)/lib/libcurl.so gnupg/Makefile |
||||
|
$(MAKE) -C gnupg |
||||
|
|
||||
|
$(LOCAL)/bin/gpg2: gnupg/g10/gpg2 gnupg/configure |
||||
|
$(MAKE) -C gnupg prefix=$(LOCAL) install |
||||
|
ls -l $(LOCAL)/bin/gpg2 |
||||
|
|
||||
|
$(LOCAL)/bin/pinentry.sh: pinentry.sh |
||||
|
install $< $(LOCAL)/bin |
||||
|
|
||||
|
gnupg-build: gnupg/g10/gpg2 |
||||
|
|
||||
|
gnupg-install: $(LOCAL)/bin/gpg2 $(LOCAL)/bin/pinentry.sh |
||||
|
install -d $(LOCAL)/etc/gnupg |
||||
|
install -d $(LOCAL)/var/run/gnupg |
||||
|
install -d $(LOCAL)/var/cache/gnupg |
||||
|
|
||||
|
gnupg-clean: |
||||
|
-$(MAKE) -C gnupg |
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# gpgme
|
||||
|
|
||||
|
gpgme/configure: gpgme gpgme/configure.ac |
||||
|
cd gpgme && ./autogen.sh |
||||
|
|
||||
|
gpgme/Makefile: gpgme/configure |
||||
|
-patch -N -p1 --reject-file=- gpgme/m4/libtool.m4 libtool-Add-Android-Linux-support.patch |
||||
|
cd gpgme && \
|
||||
|
CC="$(CC)" AR="$(AR)" RANLIB=$(RANLIB) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
|
||||
|
./configure \
|
||||
|
--host=$(HOST) \
|
||||
|
--enable-maintainer-mode \
|
||||
|
--with-gpg-error-prefix=$(LOCAL) \
|
||||
|
--with-libassuan-prefix=$(LOCAL) \
|
||||
|
--enable-fixed-path=$(prefix)/bin \
|
||||
|
--without-g13 \
|
||||
|
--disable-glibtest \
|
||||
|
--disable-gpg-test \
|
||||
|
--disable-gpgsm-test \
|
||||
|
--disable-gpgconf-test \
|
||||
|
--disable-g13-test \
|
||||
|
--prefix=$(prefix) |
||||
|
|
||||
|
|
||||
|
gpgme/src/.libs/libgpgme.so: gpgme/Makefile |
||||
|
$(MAKE) -C gpgme |
||||
|
|
||||
|
$(LOCAL)/lib/libgpgme.so: gpgme/src/.libs/libgpgme.so |
||||
|
$(MAKE) -C gpgme DESTDIR=$(DESTDIR) prefix=$(prefix) install |
||||
|
|
||||
|
gpgme-build: gpgme/src/.libs/libgpgme.so |
||||
|
|
||||
|
gpgme-install: $(LOCAL)/bin/gpg2 $(LOCAL)/lib/libgpgme.so |
||||
|
|
||||
|
gpgme-clean: |
||||
|
-$(MAKE) -C gpgme clean |
||||
|
-rm -f gpgme/Makefile gpgme/configure |
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# assets for Android app
|
||||
|
|
||||
|
clean-assets: |
||||
|
rm -rf $(ASSETS) |
||||
|
|
||||
|
ASSETS := $(EXTERNAL_ROOT)/../assets |
||||
|
assets: clean-assets |
||||
|
# add the new stuff |
||||
|
install -d $(ASSETS) |
||||
|
cp -a $(LOCAL)/* $(ASSETS) |
||||
|
# remove all the stuff we don't need |
||||
|
rm -f $(ASSETS)/bin/*-static |
||||
|
rm -f $(ASSETS)/bin/curl* |
||||
|
rm -f $(ASSETS)/lib/*.a $(ASSETS)/lib/*.la |
||||
|
# remove lib symlinks since Android AssetManager copies them as files |
||||
|
rm -f $(ASSETS)/lib/*.so |
||||
|
# remove .so.0 symlink and rename the .so.0.12.0 file to it |
||||
|
for f in $(ASSETS)/lib/*.so.[0-9]*; do \
|
||||
|
echo $$f; \
|
||||
|
test ! -L $$f || \
|
||||
|
(rm $$f && mv $$f.[0-9]* $$f); \
|
||||
|
done |
||||
|
rm -rf $(ASSETS)/include |
||||
|
rm -rf $(ASSETS)/share/man $(ASSETS)/share/info $(ASSETS)/share/doc |
||||
|
rm -rf $(ASSETS)/tests |
||||
|
|
||||
|
|
||||
|
#------------------------------------------------------------------------------#
|
||||
|
# clean
|
||||
|
|
||||
|
install-clean: |
||||
|
rm -rf -- $(LOCAL) |
||||
|
|
||||
|
clean: install-clean gnupg-clean curl-clean libksba-clean libassuan-clean npth-clean libgcrypt-clean libgpg-error-clean |
||||
|
|
||||
|
distclean: clean install-clean |
||||
|
-$(MAKE) -C gnupg distclean |
||||
|
-$(MAKE) -C curl distclean |
||||
|
-rm -f curl/configure # their distclean fails to rm this |
||||
|
-$(MAKE) -C libksba distclean |
||||
|
-$(MAKE) -C npth distclean |
||||
|
-$(MAKE) -C libassuan distclean |
||||
|
-$(MAKE) -C libgcrypt distclean |
||||
|
-$(MAKE) -C libgpg-error distclean |
||||
|
|
||||
|
gitclean: $(EXTERNAL_SRCS_CLEAN) |
||||
|
rm -rf $(ASSETS) |
||||
|
rm -rf $(EXTERNAL_ROOT)/data |
||||
|
|
@ -0,0 +1,12 @@ |
|||||
|
diff --git a/libgcrypt/tests/random.c b/libgcrypt/tests/random.c
|
||||
|
index 10bf646..a52aacc 100644
|
||||
|
--- a/libgcrypt/tests/random.c
|
||||
|
+++ b/libgcrypt/tests/random.c
|
||||
|
@@ -439,7 +439,6 @@ run_all_rng_tests (const char *program)
|
||||
|
"--early-rng-check --prefer-fips-rng", |
||||
|
"--early-rng-check --prefer-system-rng", |
||||
|
"--prefer-standard-rng", |
||||
|
- "--prefer-fips-rng",
|
||||
|
"--prefer-system-rng", |
||||
|
NULL |
||||
|
}; |
@ -0,0 +1,48 @@ |
|||||
|
commit 8eeeb00daef8c4f720c9b79a0cdb89225d9909b6 |
||||
|
Author: David 'Digit' Turner <digit@google.com> |
||||
|
Date: Tue Oct 8 14:37:32 2013 -0700 |
||||
|
|
||||
|
libtool: Add Android/Linux support. |
||||
|
|
||||
|
This patch adds proper Android support to libtool. The main |
||||
|
issues are the following: |
||||
|
|
||||
|
- Versioned libraries are not supported by the platform and |
||||
|
its build/packaging tools. |
||||
|
|
||||
|
- The dynamic linker is not GNU ld, there is no support for |
||||
|
DT_RUNPATH. |
||||
|
|
||||
|
- Similarly, there is no ldconfig. |
||||
|
|
||||
|
diff --git a/m4/libtool.m4 b/m4/libtool.m4
|
||||
|
index 80d7e44..080272c 100644
|
||||
|
--- a/m4/libtool.m4
|
||||
|
+++ b/m4/libtool.m4
|
||||
|
@@ -2683,6 +2683,26 @@ linux*oldld* | linux*aout* | linux*coff*)
|
||||
|
dynamic_linker=no |
||||
|
;; |
||||
|
|
||||
|
+linux*android*)
|
||||
|
+ version_type=none # Android doesn't support versioned libraries.
|
||||
|
+ need_lib_prefix=no
|
||||
|
+ need_version=no
|
||||
|
+ library_names_spec='$libname$release$shared_ext'
|
||||
|
+ soname_spec='$libname$release$shared_ext'
|
||||
|
+ finish_cmds=
|
||||
|
+ shlibpath_var=LD_LIBRARY_PATH
|
||||
|
+ shlibpath_overrides_runpath=yes
|
||||
|
+
|
||||
|
+ # This implies no fast_install, which is unacceptable.
|
||||
|
+ # Some rework will be needed to allow for fast_install
|
||||
|
+ # before this can be enabled.
|
||||
|
+ hardcode_into_libs=yes
|
||||
|
+
|
||||
|
+ dynamic_linker='Android linker'
|
||||
|
+ # Don't embed -rpath directories since the linker doesn't support them.
|
||||
|
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
|
||||
|
+ ;;
|
||||
|
+
|
||||
|
# This must be glibc/ELF. |
||||
|
linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) |
||||
|
version_type=linux # correct to gnu/linux during the next big refactor |
@ -0,0 +1,47 @@ |
|||||
|
#!/system/bin/sh |
||||
|
echo OK |
||||
|
while read cmd rest |
||||
|
do |
||||
|
case $cmd in |
||||
|
SETDESC) |
||||
|
DESC=$rest |
||||
|
echo OK |
||||
|
;; |
||||
|
SETPROMPT) |
||||
|
PROMPT=$rest |
||||
|
echo OK |
||||
|
;; |
||||
|
SETOK) |
||||
|
OK=$rest |
||||
|
echo OK |
||||
|
;; |
||||
|
SETERROR) |
||||
|
ERROR=$rest |
||||
|
echo OK |
||||
|
;; |
||||
|
GETPIN) |
||||
|
|
||||
|
echo "D " |
||||
|
echo OK |
||||
|
;; |
||||
|
OPTION) |
||||
|
echo OK |
||||
|
;; |
||||
|
GETINFO) |
||||
|
case $rest in |
||||
|
pid*) |
||||
|
echo D $$ |
||||
|
echo OK |
||||
|
;; |
||||
|
esac |
||||
|
;; |
||||
|
BYE) |
||||
|
echo OK |
||||
|
exit |
||||
|
;; |
||||
|
*) |
||||
|
echo OK |
||||
|
;; |
||||
|
esac |
||||
|
done |
||||
|
|
Binary file not shown.
@ -1,17 +1,251 @@ |
|||||
package org.pEp.jniadapter; |
package org.pEp.jniadapter; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.io.FileOutputStream; |
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStream; |
||||
|
import java.io.OutputStream; |
||||
|
import java.io.OutputStreamWriter; |
||||
|
import java.util.Scanner; |
||||
|
|
||||
|
import org.apache.commons.io.FileUtils; |
||||
|
import org.apache.commons.io.IOUtils; |
||||
|
|
||||
import android.content.Context; |
import android.content.Context; |
||||
|
import android.content.res.AssetManager; |
||||
|
import android.util.Log; |
||||
|
import android.content.Intent; |
||||
|
|
||||
|
import java.lang.reflect.InvocationTargetException; |
||||
|
import java.lang.reflect.Method; |
||||
|
|
||||
public class AndroidHelper { |
public class AndroidHelper { |
||||
static { |
static { |
||||
System.loadLibrary("pEpJNIAndroidHelper"); |
System.loadLibrary("pEpJNIAndroidHelper"); |
||||
} |
} |
||||
|
|
||||
|
public static final String TAG = "AndroidHelper"; |
||||
|
|
||||
private static native int setenv(String key, String value, boolean overwrite); |
private static native int setenv(String key, String value, boolean overwrite); |
||||
|
private static native int nativeSetup(String debugflag); |
||||
|
|
||||
|
private static File homeDir; |
||||
|
private static File optDir; |
||||
|
private static File versionFile; |
||||
|
public static File binDir; |
||||
|
|
||||
|
// TODO : Increment when needed.
|
||||
|
public static int VERSION_CODE = 0; |
||||
|
|
||||
|
private static File shareDir; |
||||
|
|
||||
|
private static final String dBFileName = "system.db"; |
||||
|
private static File dBfile; |
||||
|
|
||||
|
private static boolean already = false; |
||||
|
|
||||
|
public static void startDaemonIfNeeded(Context c) { |
||||
|
if (!new File(homeDir, "S.gpg-agent").exists()) { |
||||
|
Intent service = new Intent(c, GPGAgentService.class); |
||||
|
c.startService(service); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void envSetup(Context c) { |
||||
|
// "/opt" like dir to unpack GnuPG assets
|
||||
|
optDir = c.getDir("opt", Context.MODE_PRIVATE); |
||||
|
|
||||
|
// Add GnuPG's bin to PATH
|
||||
|
binDir = new File(optDir, "bin"); |
||||
|
setenv("PATH", System.getenv("PATH") + ":" + |
||||
|
binDir.getAbsolutePath(), true); |
||||
|
|
||||
|
// Tell dynamic loader where to find libs
|
||||
|
String appLibDir = ""; |
||||
|
try { |
||||
|
appLibDir = new File(c.getApplicationInfo().nativeLibraryDir).getCanonicalPath(); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
appLibDir = new File(c.getApplicationInfo().nativeLibraryDir).getAbsolutePath(); |
||||
|
} |
||||
|
File libDir = new File(optDir, "lib"); |
||||
|
setenv("LD_LIBRARY_PATH", appLibDir + ":" + |
||||
|
libDir.getAbsolutePath() + ":" + |
||||
|
System.getenv("LD_LIBRARY_PATH"), true); |
||||
|
|
||||
|
// Set HOME environment variable pointing to
|
||||
|
// something like "/data/data/app.package.name/home"
|
||||
|
// pEpEngine use it to find management DB and gpg home
|
||||
|
homeDir = c.getDir("home", Context.MODE_PRIVATE); |
||||
|
setenv("HOME", homeDir.getAbsolutePath(), true); |
||||
|
|
||||
|
// pEpEngine need to find the safe words database
|
||||
|
shareDir = c.getDir("trustwords", Context.MODE_PRIVATE); |
||||
|
dBfile = new File(shareDir, dBFileName); |
||||
|
|
||||
|
// TRUSTWORDS is absolute path of dir containig system.db
|
||||
|
setenv("TRUSTWORDS", shareDir.getAbsolutePath(), true); |
||||
|
|
||||
|
// Check version file retains latest installed version
|
||||
|
versionFile = new File(c.getFilesDir(), "VERSION"); |
||||
|
} |
||||
|
|
||||
|
public static void assetsSetup(Context c) { |
||||
|
envSetup(c); |
||||
|
boolean needUpgrade = needNewAssets(); |
||||
|
|
||||
|
// If system.db still not here, then go get it in the assets.
|
||||
|
if (dBfile.exists() && needUpgrade){ |
||||
|
dBfile.delete(); |
||||
|
} |
||||
|
if (!dBfile.exists()){ |
||||
|
assetFileExtract(c, dBFileName, shareDir); |
||||
|
} |
||||
|
|
||||
|
// Copy GnuPG binaries
|
||||
|
if (optDir.exists() && needUpgrade){ |
||||
|
try { |
||||
|
FileUtils.deleteDirectory(optDir); |
||||
|
} catch (IOException e) { |
||||
|
Log.e(TAG, "Couldn't delete existing gpg binaries"); |
||||
|
} |
||||
|
} |
||||
|
if (!optDir.exists()){ |
||||
|
optDir.mkdirs(); |
||||
|
assetPathExtract(c, "lib", optDir); |
||||
|
assetPathExtract(c, "bin", optDir); |
||||
|
new File(optDir, "var/cache/gnupg").mkdirs(); |
||||
|
new File(optDir, "var/lib/gnupg").mkdirs(); |
||||
|
new File(optDir, "var/run/gnupg").mkdirs(); |
||||
|
chmod("0755", optDir, true); |
||||
|
} |
||||
|
|
||||
|
// Fill version file
|
||||
|
setInstalledVersion(c); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static void nativeSetup(Context c) { |
||||
|
// pre-load libs for pepengine, as
|
||||
|
// android cannot solve lib dependencies on its own
|
||||
|
System.loadLibrary("gpg-error"); |
||||
|
System.loadLibrary("assuan"); |
||||
|
System.loadLibrary("gpgme"); |
||||
|
// Launch native side setup
|
||||
|
// TODO disable debug when done
|
||||
|
nativeSetup( "9:"+new File(c.getFilesDir(), "gpgme.log").getAbsolutePath()); |
||||
|
} |
||||
|
|
||||
public static void setup(Context c) { |
public static void setup(Context c) { |
||||
setenv("HOME", |
if(!already){ |
||||
c.getDir("home", Context.MODE_PRIVATE).getAbsolutePath(), |
already = true; |
||||
true); |
assetsSetup(c); |
||||
|
nativeSetup(c); |
||||
|
startDaemonIfNeeded(c); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void assetPathExtract(Context c, String assetPath, File targetDir) { |
||||
|
AssetManager assetManager = c.getAssets(); |
||||
|
|
||||
|
try { |
||||
|
String items[] = assetManager.list(assetPath); |
||||
|
if (items.length > 0) { |
||||
|
File newDir = new File(targetDir, new File(assetPath).getName()); |
||||
|
if (!newDir.exists()) |
||||
|
newDir.mkdirs(); |
||||
|
for (int i = 0; i < items.length; ++i) { |
||||
|
assetPathExtract(c, new File(assetPath, items[i]).getPath(), newDir); |
||||
|
} |
||||
|
} else { |
||||
|
assetFileExtract(c, assetPath, targetDir); |
||||
|
} |
||||
|
} catch (IOException ex) { |
||||
|
Log.e(TAG, assetPath + " : ", ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void assetFileExtract(Context c, String assetPath, File targetDir) { |
||||
|
AssetManager assetManager = c.getAssets(); |
||||
|
|
||||
|
try { |
||||
|
|
||||
|
InputStream inputStream = assetManager.open(assetPath); |
||||
|
String targetFileName = |
||||
|
new File(targetDir, |
||||
|
new File(assetPath).getName()).getAbsolutePath(); |
||||
|
OutputStream outputStream = new FileOutputStream(targetFileName); |
||||
|
IOUtils.copy(inputStream, outputStream); |
||||
|
outputStream.close(); |
||||
|
inputStream.close(); |
||||
|
Log.i(TAG, "asset " + assetPath + " extracted as " + targetFileName); |
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, assetPath + ": " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static int getInstalledVersion() { |
||||
|
int versionCode = -1; |
||||
|
if (versionFile.exists()){ |
||||
|
try { |
||||
|
Scanner scan = new Scanner(versionFile); |
||||
|
versionCode = Integer.parseInt(scan.next()); |
||||
|
scan.close(); |
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "getInstalledVersion: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
return versionCode; |
||||
|
} |
||||
|
|
||||
|
private static void setInstalledVersion(Context context) { |
||||
|
try { |
||||
|
FileOutputStream fileOutputStream = new FileOutputStream(versionFile); |
||||
|
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream); |
||||
|
outputStreamWriter.write(String.valueOf(VERSION_CODE) + "\n"); |
||||
|
outputStreamWriter.close(); |
||||
|
fileOutputStream.close(); |
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "setInstalledVersion: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static boolean needNewAssets() { |
||||
|
return VERSION_CODE != getInstalledVersion(); |
||||
|
} |
||||
|
|
||||
|
// TODO: replace with native impl, less prone to failure.
|
||||
|
public static void chmod(String modestr, File path) { |
||||
|
int err = 1; |
||||
|
try { |
||||
|
Class<?> fileUtils = Class.forName("android.os.FileUtils"); |
||||
|
Method setPermissions = fileUtils.getMethod("setPermissions", |
||||
|
String.class, int.class, int.class, int.class); |
||||
|
err = (Integer) setPermissions.invoke( |
||||
|
null, path.getAbsolutePath(), |
||||
|
Integer.parseInt(modestr, 8), |
||||
|
-1, -1); |
||||
|
if (err != 0) { |
||||
|
Log.i(TAG, "android.os.FileUtils.setPermissions() returned " + err |
||||
|
+ " for '" + path + "'"); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
Log.i(TAG, "chmod:", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void chmod(String mode, File path, boolean recursive) { |
||||
|
chmod(mode, path); |
||||
|
if (recursive) { |
||||
|
File[] paths = path.listFiles(); |
||||
|
for (File pth : paths) { |
||||
|
if (pth.isDirectory()) { |
||||
|
chmod(mode, pth, true); |
||||
|
} else { |
||||
|
chmod(mode, pth); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
@ -0,0 +1,64 @@ |
|||||
|
package org.pEp.jniadapter; |
||||
|
|
||||
|
import java.io.File; |
||||
|
|
||||
|
import android.content.Intent; |
||||
|
import android.app.Service; |
||||
|
import android.util.Log; |
||||
|
import android.os.IBinder; |
||||
|
|
||||
|
public class GPGAgentService extends Service { |
||||
|
public static final String TAG = "GPGAgentService"; |
||||
|
private AgentProcessThread process; |
||||
|
|
||||
|
class AgentProcessThread extends Thread { |
||||
|
|
||||
|
@Override |
||||
|
public void run() { |
||||
|
Log.i(TAG, "execute GPG agent"); |
||||
|
try { |
||||
|
Runtime.getRuntime().exec( |
||||
|
"gpg-agent" + |
||||
|
" --pinentry-program " + |
||||
|
new File(AndroidHelper.binDir, "pinentry.sh").getAbsolutePath() + |
||||
|
" --no-detach" + |
||||
|
" --daemon --write-env-file" + |
||||
|
" --batch" + |
||||
|
" --debug-level basic --log-file " |
||||
|
+ new File(GPGAgentService.this.getFilesDir(), "gpg-agent.log")).waitFor(); |
||||
|
Log.i(TAG, "execution terminated"); |
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "could not execute process", e); |
||||
|
} finally { |
||||
|
stopSelf(); |
||||
|
// eradicate process in critical section
|
||||
|
synchronized (GPGAgentService.this) { |
||||
|
process = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onCreate() { |
||||
|
Log.d(TAG, "onCreate"); |
||||
|
// Prepare environment for agent
|
||||
|
AndroidHelper.envSetup(this); |
||||
|
} |
||||
|
|
||||
|
public int onStartCommand(Intent intent, int flags, int startId) { |
||||
|
Log.d(TAG, "onStartCommand"); |
||||
|
// use critical section to avoid race conditions
|
||||
|
synchronized (this) { |
||||
|
process = new AgentProcessThread(); |
||||
|
process.start(); |
||||
|
} |
||||
|
return START_STICKY; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public IBinder onBind(Intent arg0) { |
||||
|
// onBind() must return null, even if binder unused
|
||||
|
return null; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue