1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-11-26 06:11:19 +00:00

Improve building wrapper and native binaries on Windows and Linux

* Do not ask for root if root is disabled in settings
* Show error in UI when libSyncthing.so is missing
* build-syncthing - Install Go on demand on windows
* build-syncthing - Install Android NDK on demand on windows
* Update README.md
* Update APK version to 0.14.51.rc3.6 / 4162
This commit is contained in:
Catfriend1 2018-09-22 18:31:36 +02:00 committed by GitHub
parent 0203aebc40
commit 73775a116d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 377 additions and 199 deletions

3
.gitignore vendored
View file

@ -32,6 +32,9 @@ gradle/wrapper/gradlew*
# Prebuilt-go # Prebuilt-go
syncthing/go syncthing/go
syncthing/go.tgz syncthing/go.tgz
syncthing/go.zip
syncthing/android-ndk-r16b
syncthing/ndk.zip
# External build artifacts # External build artifacts
ext/ ext/

View file

@ -14,7 +14,7 @@
A wrapper of [Syncthing](https://github.com/syncthing/syncthing) for Android. Head to the "releases" section for builds. Please open an issue under this fork if you need help. Important: Please don't file bugs at the upstream repository "syncthing-android" if you are using this fork. A wrapper of [Syncthing](https://github.com/syncthing/syncthing) for Android. Head to the "releases" section for builds. Please open an issue under this fork if you need help. Important: Please don't file bugs at the upstream repository "syncthing-android" if you are using this fork.
<img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_1.png" alt="screenshot 1" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_2.png" alt="screenshot 2" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_3.png" alt="screenshot 3" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_12.png" alt="screenshot 1" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_11.png" alt="screenshot 2" width="200" /> <img src="app/src/main/play/en-GB/listing/phoneScreenshots/screenshot_phone_09.png" alt="screenshot 3" width="200" />
# Translations # Translations
@ -22,21 +22,33 @@ The project is translated on [Transifex](https://www.transifex.com/projects/p/sy
# Building # Building
### Dependencies ### Prerequisites
- Android SDK (you can skip this if you are using Android Studio) - Android SDK
- Android NDK (`$ANDROID_NDK_HOME` should point at the root directory of your NDK) `You can skip this if you are using Android Studio.`
- Go (see [here](https://docs.syncthing.net/dev/building.html#prerequisites) for the required version) - Android NDK r16b
`$ANDROID_NDK_HOME environment variable should point at the root directory of your NDK. If the variable is not set, build-syncthing.py will automatically try to download and setup the NDK.`
- Go 1.9.7
`Make sure, Go is installed and available on the PATH environment variable. If Go is not found on the PATH environment variable, build-syncthing.py will automatically try to download and setup GO on the PATH.`
- Python 2.7
`Make sure, Python is installed and available on the PATH environment variable.`
### Build instructions ### Build instructions
Make sure you clone the project with Make sure you clone the project with
`git clone https://github.com/Catfriend1/syncthing-android.git --recursive`. Alternatively, run `git clone https://github.com/Catfriend1/syncthing-android.git --recursive`.
`git submodule init && git submodule update` in the project folder. Alternatively, run `git submodule init && git submodule update` in the project folder.
A Linux VM, for example running Debian, is recommended to build this. A Linux VM, for example running Debian, is recommended to build this.
Build Syncthing using `./gradlew cleanNative buildNative`. Then use `./gradlew assembleDebug` or Build Syncthing and the Syncthing-Android wrapper using the following commands:
Android Studio to build the apk. `./gradlew buildNative`
`./gradlew lint assembleDebug`
You can also use Android Studio to build the apk after you manually ran the `./gradlew buildNative` command in the repository root.
To clean up all files generated during build, use the following commands:
`./gradlew cleanNative`
`./gradlew clean`
# License # License

View file

@ -35,8 +35,8 @@ android {
applicationId "com.github.catfriend1.syncthingandroid" applicationId "com.github.catfriend1.syncthingandroid"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 26 targetSdkVersion 26
versionCode 4161 versionCode 4162
versionName "0.14.51.rc3.5" versionName "0.14.51.rc3.6"
testApplicationId 'com.github.catfriend1.syncthingandroid.test' testApplicationId 'com.github.catfriend1.syncthingandroid.test'
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
playAccountConfig = playAccountConfigs.defaultAccountConfig playAccountConfig = playAccountConfigs.defaultAccountConfig

View file

@ -39,9 +39,11 @@ import android.widget.Toast;
import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp; import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.service.Constants; import com.nutomic.syncthingandroid.service.Constants;
import com.nutomic.syncthingandroid.service.SyncthingRunnable.ExecutableNotFoundException;
import com.nutomic.syncthingandroid.util.ConfigXml; import com.nutomic.syncthingandroid.util.ConfigXml;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import javax.inject.Inject; import javax.inject.Inject;
public class FirstStartActivity extends Activity { public class FirstStartActivity extends Activity {
@ -79,7 +81,8 @@ public class FirstStartActivity extends Activity {
private Button mBackButton; private Button mBackButton;
private Button mNextButton; private Button mNextButton;
@Inject SharedPreferences mPreferences; @Inject
SharedPreferences mPreferences;
/** /**
* Handles activity behaviour depending on prerequisites. * Handles activity behaviour depending on prerequisites.
@ -479,6 +482,9 @@ public class FirstStartActivity extends Activity {
} }
try { try {
configXml = new ConfigXml(firstStartActivity); configXml = new ConfigXml(firstStartActivity);
} catch (ExecutableNotFoundException e) {
publishProgress(firstStartActivity.getString(R.string.executable_not_found, e.getMessage()));
cancel(true);
} catch (ConfigXml.OpenConfigException e) { } catch (ConfigXml.OpenConfigException e) {
publishProgress(firstStartActivity.getString(R.string.config_create_failed)); publishProgress(firstStartActivity.getString(R.string.config_create_failed));
cancel(true); cancel(true);

View file

@ -123,7 +123,6 @@ public class WebGuiActivity extends SyncthingActivity
/** /**
* Initialize WebView. * Initialize WebView.
*
* Ignore lint javascript warning as js is loaded only from our known, local service. * Ignore lint javascript warning as js is loaded only from our known, local service.
*/ */
@Override @Override
@ -134,7 +133,11 @@ public class WebGuiActivity extends SyncthingActivity
setContentView(R.layout.activity_web_gui); setContentView(R.layout.activity_web_gui);
mLoadingView = findViewById(R.id.loading); mLoadingView = findViewById(R.id.loading);
try {
mConfig = new ConfigXml(this); mConfig = new ConfigXml(this);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
loadCaCert(); loadCaCert();
mWebView = findViewById(R.id.webview); mWebView = findViewById(R.id.webview);

View file

@ -56,9 +56,13 @@ public class SyncthingRunnable implements Runnable {
private final File mSyncthingBinary; private final File mSyncthingBinary;
private String[] mCommand; private String[] mCommand;
private final File mLogFile; private final File mLogFile;
@Inject SharedPreferences mPreferences;
private final boolean mUseRoot; private final boolean mUseRoot;
@Inject NotificationHandler mNotificationHandler;
@Inject
SharedPreferences mPreferences;
@Inject
NotificationHandler mNotificationHandler;
public enum Command { public enum Command {
deviceid, // Output the device ID to the command line. deviceid, // Output the device ID to the command line.
@ -104,11 +108,15 @@ public class SyncthingRunnable implements Runnable {
@Override @Override
public void run() { public void run() {
try {
run(false); run(false);
} catch (ExecutableNotFoundException e) {
throw new RuntimeException(e.getMessage());
}
} }
@SuppressLint("WakelockTimeout") @SuppressLint("WakelockTimeout")
public String run(boolean returnStdOut) { public String run(boolean returnStdOut) throws ExecutableNotFoundException {
Boolean sendStopToService = false; Boolean sendStopToService = false;
Boolean restartSyncthingNative = false; Boolean restartSyncthingNative = false;
int exitCode; int exitCode;
@ -315,7 +323,11 @@ public class SyncthingRunnable implements Runnable {
* Manually run "sysctl fs.inotify" in a root shell terminal to check current limit. * Manually run "sysctl fs.inotify" in a root shell terminal to check current limit.
*/ */
private void increaseInotifyWatches() { private void increaseInotifyWatches() {
if (!mUseRoot || !Shell.SU.available()) { if (!mUseRoot) {
// Settings prohibit using root privileges. Cannot increase inotify limit.
return;
}
if (!Shell.SU.available()) {
Log.i(TAG, "increaseInotifyWatches: Root is not available. Cannot increase inotify limit."); Log.i(TAG, "increaseInotifyWatches: Root is not available. Cannot increase inotify limit.");
return; return;
} }
@ -327,7 +339,11 @@ public class SyncthingRunnable implements Runnable {
* Look for a running libsyncthing.so process and nice its IO. * Look for a running libsyncthing.so process and nice its IO.
*/ */
private void niceSyncthing() { private void niceSyncthing() {
if (!mUseRoot || !Shell.SU.available()) { if (!mUseRoot) {
// Settings prohibit using root privileges. Cannot nice syncthing.
return;
}
if (!Shell.SU.available()) {
Log.i(TAG_NICE, "Root is not available. Cannot nice syncthing."); Log.i(TAG_NICE, "Root is not available. Cannot nice syncthing.");
return; return;
} }
@ -482,7 +498,16 @@ public class SyncthingRunnable implements Runnable {
return targetEnv; return targetEnv;
} }
private Process setupAndLaunch(HashMap<String, String> env) throws IOException { private Process setupAndLaunch(HashMap<String, String> env) throws IOException, ExecutableNotFoundException {
// Check if "libSyncthing.so" exists.
if (mCommand.length > 0) {
File libSyncthing = new File(mCommand[0]);
if (!libSyncthing.exists()) {
Log.e(TAG, "CRITICAL - Syncthing core binary is missing in APK package location " + mCommand[0]);
throw new ExecutableNotFoundException(mCommand[0]);
}
}
if (mUseRoot) { if (mUseRoot) {
ProcessBuilder pb = new ProcessBuilder("su"); ProcessBuilder pb = new ProcessBuilder("su");
Process process = pb.start(); Process process = pb.start();
@ -508,4 +533,16 @@ public class SyncthingRunnable implements Runnable {
return pb.start(); return pb.start();
} }
} }
public class ExecutableNotFoundException extends Exception {
public ExecutableNotFoundException(String message) {
super(message);
}
public ExecutableNotFoundException(String message, Throwable throwable) {
super(message, throwable);
}
}
} }

View file

@ -120,18 +120,27 @@ public class SyncthingService extends Service {
* Indicates the current state of SyncthingService and of Syncthing itself. * Indicates the current state of SyncthingService and of Syncthing itself.
*/ */
public enum State { public enum State {
/** Service is initializing, Syncthing was not started yet. */ /**
* Service is initializing, Syncthing was not started yet.
*/
INIT, INIT,
/** Syncthing binary is starting. */ /**
* Syncthing binary is starting.
*/
STARTING, STARTING,
/** Syncthing binary is running, /**
* Syncthing binary is running,
* Rest API is available, * Rest API is available,
* RestApi class read the config and is fully initialized. * RestApi class read the config and is fully initialized.
*/ */
ACTIVE, ACTIVE,
/** Syncthing binary is shutting down. */ /**
* Syncthing binary is shutting down.
*/
DISABLED, DISABLED,
/** There is some problem that prevents Syncthing from running. */ /**
* There is some problem that prevents Syncthing from running.
*/
ERROR, ERROR,
} }
@ -141,13 +150,7 @@ public class SyncthingService extends Service {
* {@link onStartCommand}. * {@link onStartCommand}.
*/ */
private State mCurrentState = State.DISABLED; private State mCurrentState = State.DISABLED;
private ConfigXml mConfig; private ConfigXml mConfig;
private @Nullable PollWebGuiAvailableTask mPollWebGuiAvailableTask = null;
private @Nullable RestApi mApi = null;
private @Nullable EventProcessor mEventProcessor = null;
private @Nullable RunConditionMonitor mRunConditionMonitor = null;
private @Nullable SyncthingRunnable mSyncthingRunnable = null;
private StartupTask mStartupTask = null; private StartupTask mStartupTask = null;
private Thread mSyncthingRunnableThread = null; private Thread mSyncthingRunnableThread = null;
private Handler mHandler; private Handler mHandler;
@ -155,8 +158,26 @@ public class SyncthingService extends Service {
private final HashSet<OnServiceStateChangeListener> mOnServiceStateChangeListeners = new HashSet<>(); private final HashSet<OnServiceStateChangeListener> mOnServiceStateChangeListeners = new HashSet<>();
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this); private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
@Inject NotificationHandler mNotificationHandler; private @Nullable
@Inject SharedPreferences mPreferences; PollWebGuiAvailableTask mPollWebGuiAvailableTask = null;
private @Nullable
RestApi mApi = null;
private @Nullable
EventProcessor mEventProcessor = null;
private @Nullable
RunConditionMonitor mRunConditionMonitor = null;
private @Nullable
SyncthingRunnable mSyncthingRunnable = null;
@Inject
NotificationHandler mNotificationHandler;
@Inject
SharedPreferences mPreferences;
/** /**
* Object that must be locked upon accessing mCurrentState * Object that must be locked upon accessing mCurrentState
@ -245,7 +266,8 @@ public class SyncthingService extends Service {
if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) { if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
shutdown(State.INIT, () -> launchStartupTask(SyncthingRunnable.Command.main)); shutdown(State.INIT, () -> launchStartupTask(SyncthingRunnable.Command.main));
} else if (ACTION_STOP.equals(intent.getAction()) && mCurrentState == State.ACTIVE) { } else if (ACTION_STOP.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
shutdown(State.DISABLED, () -> {}); shutdown(State.DISABLED, () -> {
});
} else if (ACTION_RESET_DATABASE.equals(intent.getAction())) { } else if (ACTION_RESET_DATABASE.equals(intent.getAction())) {
Log.i(TAG, "Invoking reset of database"); Log.i(TAG, "Invoking reset of database");
shutdown(State.INIT, () -> { shutdown(State.INIT, () -> {
@ -305,7 +327,8 @@ public class SyncthingService extends Service {
return; return;
} }
Log.v(TAG, "Stopping syncthing"); Log.v(TAG, "Stopping syncthing");
shutdown(State.DISABLED, () -> {}); shutdown(State.DISABLED, () -> {
});
} }
} }
} }
@ -355,6 +378,12 @@ public class SyncthingService extends Service {
try { try {
syncthingService.mConfig = new ConfigXml(syncthingService); syncthingService.mConfig = new ConfigXml(syncthingService);
syncthingService.mConfig.updateIfNeeded(); syncthingService.mConfig.updateIfNeeded();
} catch (SyncthingRunnable.ExecutableNotFoundException e) {
syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "SycnthingRunnable.ExecutableNotFoundException");
synchronized (syncthingService.mStateLock) {
syncthingService.onServiceStateChange(State.ERROR);
}
cancel(true);
} catch (ConfigXml.OpenConfigException e) { } catch (ConfigXml.OpenConfigException e) {
syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "ConfigXml.OpenConfigException"); syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "ConfigXml.OpenConfigException");
synchronized (syncthingService.mStateLock) { synchronized (syncthingService.mStateLock) {
@ -483,21 +512,22 @@ public class SyncthingService extends Service {
mDestroyScheduled = true; mDestroyScheduled = true;
} else { } else {
Log.i(TAG, "Shutting down syncthing binary immediately"); Log.i(TAG, "Shutting down syncthing binary immediately");
shutdown(State.DISABLED, () -> {}); shutdown(State.DISABLED, () -> {
});
} }
} }
} else { } else {
// If the storage permission got revoked, we did not start the binary and // If the storage permission got revoked, we did not start the binary and
// are in State.INIT requiring an immediate shutdown of this service class. // are in State.INIT requiring an immediate shutdown of this service class.
Log.i(TAG, "Shutting down syncthing binary due to missing storage permission."); Log.i(TAG, "Shutting down syncthing binary due to missing storage permission.");
shutdown(State.DISABLED, () -> {}); shutdown(State.DISABLED, () -> {
});
} }
super.onDestroy(); super.onDestroy();
} }
/** /**
* Stop Syncthing and all helpers like event processor and api handler. * Stop Syncthing and all helpers like event processor and api handler.
*
* Sets {@link #mCurrentState} to newState, and calls onKilledListener once Syncthing is killed. * Sets {@link #mCurrentState} to newState, and calls onKilledListener once Syncthing is killed.
*/ */
private void shutdown(State newState, OnSyncthingKilled onKilledListener) { private void shutdown(State newState, OnSyncthingKilled onKilledListener) {
@ -550,7 +580,8 @@ public class SyncthingService extends Service {
onKilledListener.onKilled(); onKilledListener.onKilled();
} }
public @Nullable RestApi getApi() { public @Nullable
RestApi getApi() {
return mApi; return mApi;
} }
@ -568,7 +599,6 @@ public class SyncthingService extends Service {
/** /**
* Register a listener for the syncthing API state changing. * Register a listener for the syncthing API state changing.
*
* The listener is called immediately with the current state, and again whenever the state * The listener is called immediately with the current state, and again whenever the state
* changes. The call is always from the GUI thread. * changes. The call is always from the GUI thread.
* *
@ -692,7 +722,8 @@ public class SyncthingService extends Service {
Log.v(TAG, "importConfig BEGIN"); Log.v(TAG, "importConfig BEGIN");
// Shutdown synchronously. // Shutdown synchronously.
shutdown(State.DISABLED, () -> {}); shutdown(State.DISABLED, () -> {
});
// Import config, privateKey and/or publicKey. // Import config, privateKey and/or publicKey.
try { try {
@ -736,11 +767,13 @@ public class SyncthingService extends Service {
// Preferences that are no longer used and left-overs from previous versions of the app. // Preferences that are no longer used and left-overs from previous versions of the app.
case "first_start": case "first_start":
case "notify_crashes": case "notify_crashes":
Log.v(TAG, "importConfig: Ignoring deprecated pref \"" + prefKey + "\".");
break;
// Cached information which is not available on SettingsActivity. // Cached information which is not available on SettingsActivity.
case Constants.PREF_DEBUG_FACILITIES_AVAILABLE: case Constants.PREF_DEBUG_FACILITIES_AVAILABLE:
case Constants.PREF_EVENT_PROCESSOR_LAST_SYNC_ID: case Constants.PREF_EVENT_PROCESSOR_LAST_SYNC_ID:
case Constants.PREF_LAST_BINARY_VERSION: case Constants.PREF_LAST_BINARY_VERSION:
Log.v(TAG, "importConfig: Ignoring pref \"" + prefKey + "\"."); Log.v(TAG, "importConfig: Ignoring cache pref \"" + prefKey + "\".");
break; break;
default: default:
Log.v(TAG, "importConfig: Adding pref \"" + prefKey + "\" to commit ..."); Log.v(TAG, "importConfig: Adding pref \"" + prefKey + "\" to commit ...");

View file

@ -41,7 +41,6 @@ import javax.xml.transform.stream.StreamResult;
/** /**
* Provides direct access to the config.xml file in the file system. * Provides direct access to the config.xml file in the file system.
*
* This class should only be used if the syncthing API is not available (usually during startup). * This class should only be used if the syncthing API is not available (usually during startup).
*/ */
public class ConfigXml { public class ConfigXml {
@ -53,19 +52,21 @@ public class ConfigXml {
private static final int FOLDER_ID_APPENDIX_LENGTH = 4; private static final int FOLDER_ID_APPENDIX_LENGTH = 4;
private final Context mContext; private final Context mContext;
@Inject SharedPreferences mPreferences;
@Inject
SharedPreferences mPreferences;
private final File mConfigFile; private final File mConfigFile;
private Document mConfig; private Document mConfig;
public ConfigXml(Context context) throws OpenConfigException { public ConfigXml(Context context) throws OpenConfigException, SyncthingRunnable.ExecutableNotFoundException {
mContext = context; mContext = context;
mConfigFile = Constants.getConfigFile(mContext); mConfigFile = Constants.getConfigFile(mContext);
boolean isFirstStart = !mConfigFile.exists(); boolean isFirstStart = !mConfigFile.exists();
if (isFirstStart) { if (isFirstStart) {
Log.i(TAG, "App started for the first time. Generating keys and config."); Log.i(TAG, "App started for the first time. Generating keys and config.");
new SyncthingRunnable(context, SyncthingRunnable.Command.generate).run(); new SyncthingRunnable(context, SyncthingRunnable.Command.generate).run(true);
} }
readConfig(); readConfig();
@ -122,7 +123,6 @@ public class ConfigXml {
/** /**
* Updates the config file. * Updates the config file.
*
* Sets ignorePerms flag to true on every folder, force enables TLS, sets the * Sets ignorePerms flag to true on every folder, force enables TLS, sets the
* username/password, and disables weak hash checking. * username/password, and disables weak hash checking.
*/ */
@ -210,7 +210,6 @@ public class ConfigXml {
/** /**
* Updates syncthing options to a version specific target setting in the config file. * Updates syncthing options to a version specific target setting in the config file.
*
* Used for one-time config migration from a lower syncthing version to the current version. * Used for one-time config migration from a lower syncthing version to the current version.
* Enables filesystem watcher. * Enables filesystem watcher.
* Returns if changes to the config have been made. * Returns if changes to the config have been made.
@ -273,7 +272,6 @@ public class ConfigXml {
/** /**
* Set device model name as device name for Syncthing. * Set device model name as device name for Syncthing.
*
* We need to iterate through XML nodes manually, as mConfig.getDocumentElement() will also * We need to iterate through XML nodes manually, as mConfig.getDocumentElement() will also
* return nested elements inside folder element. We have to check that we only rename the * return nested elements inside folder element. We have to check that we only rename the
* device corresponding to the local device ID. * device corresponding to the local device ID.

View file

@ -652,6 +652,7 @@ Please report any problems you encounter via Github.</string>
<string name="syncthing_terminated">Syncthing was terminated</string> <string name="syncthing_terminated">Syncthing was terminated</string>
<!-- Toast shown if syncthing failed to create or read the config --> <!-- Toast shown if syncthing failed to create or read the config -->
<string name="executable_not_found">Core executable \"%s\" is missing. Check build and logcat output.</string>
<string name="config_create_failed">Failed to create configuration. Check logcat output.</string> <string name="config_create_failed">Failed to create configuration. Check logcat output.</string>
<string name="config_read_failed">Failed to read configuration. Consider backing up data from your sync folders, then clear this app\'s data from Android settings and launch it again.</string> <string name="config_read_failed">Failed to read configuration. Consider backing up data from your sync folders, then clear this app\'s data from Android settings and launch it again.</string>

View file

@ -44,17 +44,13 @@ def get_min_sdk(project_dir):
fail('Failed to find minSdkVersion') fail('Failed to find minSdkVersion')
def get_ndk_home():
if not os.environ.get('ANDROID_NDK_HOME', ''):
fail('Error: ANDROID_NDK_HOME environment variable not defined')
return os.environ['ANDROID_NDK_HOME']
def which(program): def which(program):
import os import os
def is_exe(fpath): def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK) return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
if (sys.platform == 'win32'):
program += ".exe"
fpath, fname = os.path.split(program) fpath, fname = os.path.split(program)
if fpath: if fpath:
if is_exe(program): if is_exe(program):
@ -67,26 +63,40 @@ def which(program):
return None return None
def change_permissions_recursive(path, mode):
import os
for root, dirs, files in os.walk(path, topdown=False):
for dir in [os.path.join(root,d) for d in dirs]:
os.chmod(dir, mode)
for file in [os.path.join(root, f) for f in files]:
os.chmod(file, mode)
def install_go(): def install_go():
import os import os
import tarfile import tarfile
import zipfile
import urllib import urllib
import hashlib import hashlib
# Consts. # Consts.
pwd_path = os.path.dirname(os.path.realpath(__file__)) pwd_path = os.path.dirname(os.path.realpath(__file__))
if sys.platform == 'win32':
url = 'https://dl.google.com/go/go1.9.7.windows-amd64.zip'
expected_shasum = '8db4b21916a3bc79f48d0611202ee5814c82f671b36d5d2efcb446879456cd28'
tar_gz_fullfn = pwd_path + os.path.sep + 'go.zip';
else:
url = 'https://dl.google.com/go/go1.9.7.linux-amd64.tar.gz' url = 'https://dl.google.com/go/go1.9.7.linux-amd64.tar.gz'
expected_shasum = '88573008f4f6233b81f81d8ccf92234b4f67238df0f0ab173d75a302a1f3d6ee' expected_shasum = '88573008f4f6233b81f81d8ccf92234b4f67238df0f0ab173d75a302a1f3d6ee'
tar_gz_fullfn = pwd_path + os.path.sep + 'go.tgz';
# Download prebuilt-go. # Download prebuilt-go.
url_base_name = os.path.basename(url) url_base_name = os.path.basename(url)
tar_gz_fullfn = pwd_path + os.path.sep + 'go.tgz';
if not os.path.isfile(tar_gz_fullfn): if not os.path.isfile(tar_gz_fullfn):
print('Downloading prebuilt-go tar to:', tar_gz_fullfn) print('Downloading prebuilt-go to:', tar_gz_fullfn)
tar_gz_fullfn = urllib.urlretrieve(url, tar_gz_fullfn)[0] tar_gz_fullfn = urllib.urlretrieve(url, tar_gz_fullfn)[0]
print('Downloaded prebuilt-go tar to:', tar_gz_fullfn) print('Downloaded prebuilt-go to:', tar_gz_fullfn)
# Verfiy SHA-1 checksum of downloaded files. # Verfiy SHA-256 checksum of downloaded files.
with open(tar_gz_fullfn, 'rb') as f: with open(tar_gz_fullfn, 'rb') as f:
contents = f.read() contents = f.read()
found_shasum = hashlib.sha256(contents).hexdigest() found_shasum = hashlib.sha256(contents).hexdigest()
@ -96,11 +106,18 @@ def install_go():
print("[ok] Checksum of", tar_gz_fullfn, "matches expected value.") print("[ok] Checksum of", tar_gz_fullfn, "matches expected value.")
# Proceed with extraction of the prebuilt go. # Proceed with extraction of the prebuilt go.
# This will go to a subfolder "go" in the current path. if not os.path.isfile(pwd_path + os.path.sep + 'go' + os.path.sep + 'LICENSE'):
print("Extracting prebuilt-go ...") print("Extracting prebuilt-go ...")
# This will go to a subfolder "go" in the current path.
file_name, file_extension = os.path.splitext(url_base_name) file_name, file_extension = os.path.splitext(url_base_name)
if sys.platform == 'win32':
zip = zipfile.ZipFile(tar_gz_fullfn, 'r')
zip.extractall(pwd_path)
zip.close()
else:
tar = tarfile.open(tar_gz_fullfn) tar = tarfile.open(tar_gz_fullfn)
tar.extractall(pwd_path) tar.extractall(pwd_path)
tar.close()
# Add (...).tar/go/bin" to the PATH. # Add (...).tar/go/bin" to the PATH.
go_bin_path = pwd_path + os.path.sep + 'go' + os.path.sep + 'bin' go_bin_path = pwd_path + os.path.sep + 'go' + os.path.sep + 'bin'
@ -108,6 +125,65 @@ def install_go():
os.environ["PATH"] += os.pathsep + go_bin_path os.environ["PATH"] += os.pathsep + go_bin_path
def install_ndk():
import os
import zipfile
import urllib
import hashlib
# Consts.
pwd_path = os.path.dirname(os.path.realpath(__file__))
if sys.platform == 'win32':
url = 'https://dl.google.com/android/repository/android-ndk-r16b-windows-x86_64.zip'
expected_shasum = 'f3f1909ed1052e98dda2c79d11c22f3da28daf25'
else:
url = 'https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip'
expected_shasum = '42aa43aae89a50d1c66c3f9fdecd676936da6128'
zip_fullfn = pwd_path + os.path.sep + 'ndk.zip';
# Download NDK.
url_base_name = os.path.basename(url)
if not os.path.isfile(zip_fullfn):
print('Downloading NDK to:', zip_fullfn)
zip_fullfn = urllib.urlretrieve(url, zip_fullfn)[0]
print('Downloaded NDK to:', zip_fullfn)
# Verfiy SHA-1 checksum of downloaded files.
with open(zip_fullfn, 'rb') as f:
contents = f.read()
found_shasum = hashlib.sha1(contents).hexdigest()
print("SHA-1:", zip_fullfn, "%s" % found_shasum)
if found_shasum != expected_shasum:
fail('Error: SHA-1 checksum', found_shasum, 'of downloaded file does not match expected checksum', expected_shasum)
print("[ok] Checksum of", zip_fullfn, "matches expected value.")
# Proceed with extraction of the NDK if necessary.
ndk_home_path = pwd_path + os.path.sep + 'android-ndk-r16b'
if not os.path.isfile(ndk_home_path + os.path.sep + "sysroot" + os.path.sep + "NOTICE"):
print("Extracting NDK ...")
# This will go to a subfolder "android-ndk-r16b" in the current path.
file_name, file_extension = os.path.splitext(url_base_name)
zip = zipfile.ZipFile(zip_fullfn, 'r')
zip.extractall(pwd_path)
zip.close()
# Linux only - Set executable permission on files.
if platform.system() == 'Linux':
print("Setting permissions on NDK executables ...")
change_permissions_recursive(ndk_home_path, 0o755);
# Add "ANDROID_NDK_HOME" environment variable.
print('Adding ANDROID_NDK_HOME=\'' + ndk_home_path + '\'')
os.environ["ANDROID_NDK_HOME"] = ndk_home_path
#
# BUILD SCRIPT MAIN.
#
if platform.system() not in SUPPORTED_PYTHON_PLATFORMS: if platform.system() not in SUPPORTED_PYTHON_PLATFORMS:
fail('Unsupported python platform %s. Supported platforms: %s', platform.system(), fail('Unsupported python platform %s. Supported platforms: %s', platform.system(),
', '.join(SUPPORTED_PYTHON_PLATFORMS)) ', '.join(SUPPORTED_PYTHON_PLATFORMS))
@ -125,12 +201,20 @@ go_bin = which("go");
if not go_bin: if not go_bin:
print('Warning: go is not available on the PATH.') print('Warning: go is not available on the PATH.')
install_go(); install_go();
# Retry: Check if go is available. # Retry: Check if go is available.
go_bin = which("go"); go_bin = which("go");
if not go_bin: if not go_bin:
fail('Error: go is not available on the PATH.') fail('Error: go is not available on the PATH.')
print ('go_bin [', go_bin, ']') print ('go_bin=\'' + go_bin + '\'')
# Check if ANDROID_NDK_HOME variable is set.
if not os.environ.get('ANDROID_NDK_HOME', ''):
print('Warning: ANDROID_NDK_HOME environment variable not defined.')
install_ndk();
# Retry: Check if ANDROID_NDK_HOME variable is set.
if not os.environ.get('ANDROID_NDK_HOME', ''):
fail('Error: ANDROID_NDK_HOME environment variable not defined')
print ('ANDROID_NDK_HOME=\'' + os.environ.get('ANDROID_NDK_HOME', '') + '\'')
# Make sure all tags are available for git describe # Make sure all tags are available for git describe
# https://github.com/syncthing/syncthing-android/issues/872 # https://github.com/syncthing/syncthing-android/issues/872
@ -148,7 +232,7 @@ for target in BUILD_TARGETS:
if os.environ.get('SYNCTHING_ANDROID_PREBUILT', ''): if os.environ.get('SYNCTHING_ANDROID_PREBUILT', ''):
# The environment variable indicates the SDK and stdlib was prebuilt, set a custom paths. # The environment variable indicates the SDK and stdlib was prebuilt, set a custom paths.
standalone_ndk_dir = get_ndk_home() + os.path.sep + 'standalone-ndk' + os.path.sep + 'android-' + target_min_sdk + '-' + target['goarch'] standalone_ndk_dir = os.environ['ANDROID_NDK_HOME'] + os.path.sep + 'standalone-ndk' + os.path.sep + 'android-' + target_min_sdk + '-' + target['goarch']
pkg_argument = [] pkg_argument = []
else: else:
# Build standalone NDK toolchain if it doesn't exist. # Build standalone NDK toolchain if it doesn't exist.
@ -160,7 +244,7 @@ for target in BUILD_TARGETS:
print('Building standalone NDK for', target['arch'], 'API level', target_min_sdk, 'to', standalone_ndk_dir) print('Building standalone NDK for', target['arch'], 'API level', target_min_sdk, 'to', standalone_ndk_dir)
subprocess.check_call([ subprocess.check_call([
sys.executable, sys.executable,
os.path.join(get_ndk_home(), 'build', 'tools', 'make_standalone_toolchain.py'), os.path.join(os.environ['ANDROID_NDK_HOME'], 'build', 'tools', 'make_standalone_toolchain.py'),
'--arch', '--arch',
target['arch'], target['arch'],
'--api', '--api',

View file

@ -10,6 +10,7 @@ task buildNative(type: Exec) {
*/ */
task cleanNative(type: Delete) { task cleanNative(type: Delete) {
delete "$projectDir/../app/src/main/jniLibs/" delete "$projectDir/../app/src/main/jniLibs/"
delete "android-ndk-r16b"
delete "gobuild" delete "gobuild"
delete "go" delete "go"
} }