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
syncthing/go
syncthing/go.tgz
syncthing/go.zip
syncthing/android-ndk-r16b
syncthing/ndk.zip
# External build artifacts
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.
<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
@ -22,21 +22,33 @@ The project is translated on [Transifex](https://www.transifex.com/projects/p/sy
# Building
### Dependencies
- Android SDK (you can skip this if you are using Android Studio)
- Android NDK (`$ANDROID_NDK_HOME` should point at the root directory of your NDK)
- Go (see [here](https://docs.syncthing.net/dev/building.html#prerequisites) for the required version)
### Prerequisites
- Android SDK
`You can skip this if you are using Android Studio.`
- 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
Make sure you clone the project with
`git clone https://github.com/Catfriend1/syncthing-android.git --recursive`. Alternatively, run
`git submodule init && git submodule update` in the project folder.
`git clone https://github.com/Catfriend1/syncthing-android.git --recursive`.
Alternatively, run `git submodule init && git submodule update` in the project folder.
A Linux VM, for example running Debian, is recommended to build this.
Build Syncthing using `./gradlew cleanNative buildNative`. Then use `./gradlew assembleDebug` or
Android Studio to build the apk.
Build Syncthing and the Syncthing-Android wrapper using the following commands:
`./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

View file

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

View file

@ -39,9 +39,11 @@ import android.widget.Toast;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.service.Constants;
import com.nutomic.syncthingandroid.service.SyncthingRunnable.ExecutableNotFoundException;
import com.nutomic.syncthingandroid.util.ConfigXml;
import java.lang.ref.WeakReference;
import javax.inject.Inject;
public class FirstStartActivity extends Activity {
@ -55,7 +57,7 @@ public class FirstStartActivity extends Activity {
public int dotColorActive;
public int dotColorInActive;
Slide (int layout, int dotColorActive, int dotColorInActive) {
Slide(int layout, int dotColorActive, int dotColorInActive) {
this.layout = layout;
this.dotColorActive = dotColorActive;
this.dotColorInActive = dotColorInActive;
@ -79,7 +81,8 @@ public class FirstStartActivity extends Activity {
private Button mBackButton;
private Button mNextButton;
@Inject SharedPreferences mPreferences;
@Inject
SharedPreferences mPreferences;
/**
* Handles activity behaviour depending on prerequisites.
@ -365,7 +368,7 @@ public class FirstStartActivity extends Activity {
* so that back navigation works as expected.
*/
if (mPreferences.getBoolean(Constants.PREF_START_INTO_WEB_GUI, false)) {
startActivities(new Intent[] {mainIntent, new Intent(this, WebGuiActivity.class)});
startActivities(new Intent[]{mainIntent, new Intent(this, WebGuiActivity.class)});
} else {
startActivity(mainIntent);
}
@ -479,6 +482,9 @@ public class FirstStartActivity extends Activity {
}
try {
configXml = new ConfigXml(firstStartActivity);
} catch (ExecutableNotFoundException e) {
publishProgress(firstStartActivity.getString(R.string.executable_not_found, e.getMessage()));
cancel(true);
} catch (ConfigXml.OpenConfigException e) {
publishProgress(firstStartActivity.getString(R.string.config_create_failed));
cancel(true);

View file

@ -83,7 +83,7 @@ public class WebGuiActivity extends SyncthingActivity
SslCertificate sslCert = error.getCertificate();
Field f = sslCert.getClass().getDeclaredField("mX509Certificate");
f.setAccessible(true);
X509Certificate cert = (X509Certificate)f.get(sslCert);
X509Certificate cert = (X509Certificate) f.get(sslCert);
if (cert == null) {
Log.w(TAG, "X509Certificate reference invalid");
handler.cancel();
@ -91,8 +91,8 @@ public class WebGuiActivity extends SyncthingActivity
}
cert.verify(mCaCert.getPublicKey());
handler.proceed();
} catch (NoSuchFieldException|IllegalAccessException|CertificateException|
NoSuchAlgorithmException|InvalidKeyException|NoSuchProviderException|
} catch (NoSuchFieldException | IllegalAccessException | CertificateException |
NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException |
SignatureException e) {
Log.w(TAG, e);
handler.cancel();
@ -106,7 +106,7 @@ public class WebGuiActivity extends SyncthingActivity
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
if(uri.getHost().equals(getService().getWebGuiUrl().getHost())) {
if (uri.getHost().equals(getService().getWebGuiUrl().getHost())) {
return false;
} else {
startActivity(new Intent(Intent.ACTION_VIEW, uri));
@ -123,7 +123,6 @@ public class WebGuiActivity extends SyncthingActivity
/**
* Initialize WebView.
*
* Ignore lint javascript warning as js is loaded only from our known, local service.
*/
@Override
@ -134,7 +133,11 @@ public class WebGuiActivity extends SyncthingActivity
setContentView(R.layout.activity_web_gui);
mLoadingView = findViewById(R.id.loading);
try {
mConfig = new ConfigXml(this);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
loadCaCert();
mWebView = findViewById(R.id.webview);
@ -222,7 +225,7 @@ public class WebGuiActivity extends SyncthingActivity
CertificateFactory cf = CertificateFactory.getInstance("X.509");
mCaCert = (X509Certificate)
cf.generateCertificate(inStream);
} catch (FileNotFoundException|CertificateException e) {
} catch (FileNotFoundException | CertificateException e) {
throw new IllegalArgumentException("Untrusted Certificate", e);
} finally {
try {

View file

@ -56,9 +56,13 @@ public class SyncthingRunnable implements Runnable {
private final File mSyncthingBinary;
private String[] mCommand;
private final File mLogFile;
@Inject SharedPreferences mPreferences;
private final boolean mUseRoot;
@Inject NotificationHandler mNotificationHandler;
@Inject
SharedPreferences mPreferences;
@Inject
NotificationHandler mNotificationHandler;
public enum Command {
deviceid, // Output the device ID to the command line.
@ -83,19 +87,19 @@ public class SyncthingRunnable implements Runnable {
mUseRoot = mPreferences.getBoolean(Constants.PREF_USE_ROOT, false) && Shell.SU.available();
switch (command) {
case deviceid:
mCommand = new String[]{ mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "--device-id" };
mCommand = new String[]{mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "--device-id"};
break;
case generate:
mCommand = new String[]{ mSyncthingBinary.getPath(), "-generate", mContext.getFilesDir().toString(), "-logflags=0" };
mCommand = new String[]{mSyncthingBinary.getPath(), "-generate", mContext.getFilesDir().toString(), "-logflags=0"};
break;
case main:
mCommand = new String[]{ mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-no-browser", "-logflags=0" };
mCommand = new String[]{mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-no-browser", "-logflags=0"};
break;
case resetdatabase:
mCommand = new String[]{ mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-reset-database", "-logflags=0" };
mCommand = new String[]{mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-reset-database", "-logflags=0"};
break;
case resetdeltas:
mCommand = new String[]{ mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-reset-deltas", "-logflags=0" };
mCommand = new String[]{mSyncthingBinary.getPath(), "-home", mContext.getFilesDir().toString(), "-reset-deltas", "-logflags=0"};
break;
default:
throw new InvalidParameterException("Unknown command option");
@ -104,11 +108,15 @@ public class SyncthingRunnable implements Runnable {
@Override
public void run() {
try {
run(false);
} catch (ExecutableNotFoundException e) {
throw new RuntimeException(e.getMessage());
}
}
@SuppressLint("WakelockTimeout")
public String run(boolean returnStdOut) {
public String run(boolean returnStdOut) throws ExecutableNotFoundException {
Boolean sendStopToService = false;
Boolean restartSyncthingNative = false;
int exitCode;
@ -122,7 +130,7 @@ public class SyncthingRunnable implements Runnable {
ProcessBuilder pb = new ProcessBuilder("chmod", "500", mSyncthingBinary.getPath());
Process p = pb.start();
p.waitFor();
} catch (IOException|InterruptedException e) {
} catch (IOException | InterruptedException e) {
Log.w(TAG, "Failed to chmod Syncthing", e);
}
// Loop Syncthing
@ -315,7 +323,11 @@ public class SyncthingRunnable implements Runnable {
* Manually run "sysctl fs.inotify" in a root shell terminal to check current limit.
*/
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.");
return;
}
@ -327,7 +339,11 @@ public class SyncthingRunnable implements Runnable {
* Look for a running libsyncthing.so process and nice its IO.
*/
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.");
return;
}
@ -482,7 +498,16 @@ public class SyncthingRunnable implements Runnable {
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) {
ProcessBuilder pb = new ProcessBuilder("su");
Process process = pb.start();
@ -508,4 +533,16 @@ public class SyncthingRunnable implements Runnable {
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.
*/
public enum State {
/** Service is initializing, Syncthing was not started yet. */
/**
* Service is initializing, Syncthing was not started yet.
*/
INIT,
/** Syncthing binary is starting. */
/**
* Syncthing binary is starting.
*/
STARTING,
/** Syncthing binary is running,
/**
* Syncthing binary is running,
* Rest API is available,
* RestApi class read the config and is fully initialized.
*/
ACTIVE,
/** Syncthing binary is shutting down. */
/**
* Syncthing binary is shutting down.
*/
DISABLED,
/** There is some problem that prevents Syncthing from running. */
/**
* There is some problem that prevents Syncthing from running.
*/
ERROR,
}
@ -141,13 +150,7 @@ public class SyncthingService extends Service {
* {@link onStartCommand}.
*/
private State mCurrentState = State.DISABLED;
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 Thread mSyncthingRunnableThread = null;
private Handler mHandler;
@ -155,8 +158,26 @@ public class SyncthingService extends Service {
private final HashSet<OnServiceStateChangeListener> mOnServiceStateChangeListeners = new HashSet<>();
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
@Inject NotificationHandler mNotificationHandler;
@Inject SharedPreferences mPreferences;
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;
@Inject
NotificationHandler mNotificationHandler;
@Inject
SharedPreferences mPreferences;
/**
* Object that must be locked upon accessing mCurrentState
@ -223,7 +244,7 @@ public class SyncthingService extends Service {
* See {@link mLastDeterminedShouldRun} defaulting to "false".
*/
if (mCurrentState == State.DISABLED) {
synchronized(mStateLock) {
synchronized (mStateLock) {
onServiceStateChange(mCurrentState);
}
}
@ -245,7 +266,8 @@ public class SyncthingService extends Service {
if (ACTION_RESTART.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
shutdown(State.INIT, () -> launchStartupTask(SyncthingRunnable.Command.main));
} else if (ACTION_STOP.equals(intent.getAction()) && mCurrentState == State.ACTIVE) {
shutdown(State.DISABLED, () -> {});
shutdown(State.DISABLED, () -> {
});
} else if (ACTION_RESET_DATABASE.equals(intent.getAction())) {
Log.i(TAG, "Invoking reset of database");
shutdown(State.INIT, () -> {
@ -305,7 +327,8 @@ public class SyncthingService extends Service {
return;
}
Log.v(TAG, "Stopping syncthing");
shutdown(State.DISABLED, () -> {});
shutdown(State.DISABLED, () -> {
});
}
}
}
@ -313,9 +336,9 @@ public class SyncthingService extends Service {
/**
* Prepares to launch the syncthing binary.
*/
private void launchStartupTask (SyncthingRunnable.Command srCommand) {
private void launchStartupTask(SyncthingRunnable.Command srCommand) {
Log.v(TAG, "Starting syncthing");
synchronized(mStateLock) {
synchronized (mStateLock) {
if (mCurrentState != State.DISABLED && mCurrentState != State.INIT) {
Log.e(TAG, "launchStartupTask: Wrong state " + mCurrentState + " detected. Cancelling.");
return;
@ -355,6 +378,12 @@ public class SyncthingService extends Service {
try {
syncthingService.mConfig = new ConfigXml(syncthingService);
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) {
syncthingService.mNotificationHandler.showCrashedNotification(R.string.config_read_failed, "ConfigXml.OpenConfigException");
synchronized (syncthingService.mStateLock) {
@ -483,21 +512,22 @@ public class SyncthingService extends Service {
mDestroyScheduled = true;
} else {
Log.i(TAG, "Shutting down syncthing binary immediately");
shutdown(State.DISABLED, () -> {});
shutdown(State.DISABLED, () -> {
});
}
}
} else {
// 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.
Log.i(TAG, "Shutting down syncthing binary due to missing storage permission.");
shutdown(State.DISABLED, () -> {});
shutdown(State.DISABLED, () -> {
});
}
super.onDestroy();
}
/**
* Stop Syncthing and all helpers like event processor and api handler.
*
* Sets {@link #mCurrentState} to newState, and calls onKilledListener once Syncthing is killed.
*/
private void shutdown(State newState, OnSyncthingKilled onKilledListener) {
@ -510,7 +540,7 @@ public class SyncthingService extends Service {
}
Log.i(TAG, "Shutting down");
synchronized(mStateLock) {
synchronized (mStateLock) {
onServiceStateChange(newState);
}
@ -550,7 +580,8 @@ public class SyncthingService extends Service {
onKilledListener.onKilled();
}
public @Nullable RestApi getApi() {
public @Nullable
RestApi getApi() {
return mApi;
}
@ -568,7 +599,6 @@ public class SyncthingService extends Service {
/**
* Register a listener for the syncthing API state changing.
*
* The listener is called immediately with the current state, and again whenever the state
* changes. The call is always from the GUI thread.
*
@ -692,7 +722,8 @@ public class SyncthingService extends Service {
Log.v(TAG, "importConfig BEGIN");
// Shutdown synchronously.
shutdown(State.DISABLED, () -> {});
shutdown(State.DISABLED, () -> {
});
// Import config, privateKey and/or publicKey.
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.
case "first_start":
case "notify_crashes":
Log.v(TAG, "importConfig: Ignoring deprecated pref \"" + prefKey + "\".");
break;
// Cached information which is not available on SettingsActivity.
case Constants.PREF_DEBUG_FACILITIES_AVAILABLE:
case Constants.PREF_EVENT_PROCESSOR_LAST_SYNC_ID:
case Constants.PREF_LAST_BINARY_VERSION:
Log.v(TAG, "importConfig: Ignoring pref \"" + prefKey + "\".");
Log.v(TAG, "importConfig: Ignoring cache pref \"" + prefKey + "\".");
break;
default:
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.
*
* This class should only be used if the syncthing API is not available (usually during startup).
*/
public class ConfigXml {
@ -53,19 +52,21 @@ public class ConfigXml {
private static final int FOLDER_ID_APPENDIX_LENGTH = 4;
private final Context mContext;
@Inject SharedPreferences mPreferences;
@Inject
SharedPreferences mPreferences;
private final File mConfigFile;
private Document mConfig;
public ConfigXml(Context context) throws OpenConfigException {
public ConfigXml(Context context) throws OpenConfigException, SyncthingRunnable.ExecutableNotFoundException {
mContext = context;
mConfigFile = Constants.getConfigFile(mContext);
boolean isFirstStart = !mConfigFile.exists();
if (isFirstStart) {
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();
@ -122,7 +123,6 @@ public class ConfigXml {
/**
* Updates the config file.
*
* Sets ignorePerms flag to true on every folder, force enables TLS, sets the
* username/password, and disables weak hash checking.
*/
@ -210,12 +210,11 @@ public class ConfigXml {
/**
* 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.
* Enables filesystem watcher.
* Returns if changes to the config have been made.
*/
private boolean migrateSyncthingOptions () {
private boolean migrateSyncthingOptions() {
/* Read existing config version */
int iConfigVersion = Integer.parseInt(mConfig.getDocumentElement().getAttribute("version"));
int iOldConfigVersion = iConfigVersion;
@ -273,7 +272,6 @@ public class ConfigXml {
/**
* Set device model name as device name for Syncthing.
*
* 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
* 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>
<!-- 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_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')
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):
import os
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
if (sys.platform == 'win32'):
program += ".exe"
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
@ -67,26 +63,40 @@ def which(program):
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():
import os
import tarfile
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/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'
expected_shasum = '88573008f4f6233b81f81d8ccf92234b4f67238df0f0ab173d75a302a1f3d6ee'
tar_gz_fullfn = pwd_path + os.path.sep + 'go.tgz';
# Download prebuilt-go.
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):
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]
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:
contents = f.read()
found_shasum = hashlib.sha256(contents).hexdigest()
@ -96,11 +106,18 @@ def install_go():
print("[ok] Checksum of", tar_gz_fullfn, "matches expected value.")
# 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 ...")
# This will go to a subfolder "go" in the current path.
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.extractall(pwd_path)
tar.close()
# Add (...).tar/go/bin" to the PATH.
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
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:
fail('Unsupported python platform %s. Supported platforms: %s', platform.system(),
', '.join(SUPPORTED_PYTHON_PLATFORMS))
@ -125,12 +201,20 @@ go_bin = which("go");
if not go_bin:
print('Warning: go is not available on the PATH.')
install_go();
# Retry: Check if go is available.
go_bin = which("go");
if not go_bin:
# Retry: Check if go is available.
go_bin = which("go");
if not go_bin:
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
# https://github.com/syncthing/syncthing-android/issues/872
@ -148,7 +232,7 @@ for target in BUILD_TARGETS:
if os.environ.get('SYNCTHING_ANDROID_PREBUILT', ''):
# 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 = []
else:
# 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)
subprocess.check_call([
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',
target['arch'],
'--api',

View file

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