diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 00000000..da5eb69d --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1,22 @@ +# Ignore generated files +*.deb +*.dsc +*.changes +*.build +*.buildinfo +*.tar.gz + +# Ignore files generated during build +files +autoreconf.* +*.substvars +*.log +*.debhelper +*-stamp +/*/ +!missing-sources/ +!patches/ +!patches/*.patch +!source/ +!tests/ +!upstream/ diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..9b29bec4 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,28 @@ +scrcpy (1.16-1) unstable; urgency=medium + + * build 1.16. + * + + -- zhaozhen Wed, 02 Sep 2020 18:23:17 +0800 + +scrcpy (1.14-1) unstable; urgency=medium + + * New upstream release + * Bump Standards-Version to 4.5.0 + * Bump debhelper compat to 13 + + -- Yangfl Wed, 01 Jul 2020 14:59:53 +0800 + +scrcpy (1.12.1+ds-1) unstable; urgency=medium + + * New upstream release (Closes: #947465) + * Recommend but not depend on adb + * Add upstream metadata + + -- Yangfl Tue, 14 Jan 2020 13:14:59 +0800 + +scrcpy (1.11+ds-1) unstable; urgency=medium + + * Initial release (Closes: #893279) + + -- Yangfl Wed, 20 Nov 2019 23:40:21 +0800 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..58641127 --- /dev/null +++ b/debian/control @@ -0,0 +1,44 @@ +Source: scrcpy +Section: net +Priority: optional +Maintainer: Yangfl +Build-Depends: + debhelper-compat (= 13), +Build-Depends-Arch: + meson, + libavcodec-dev, + libavformat-dev, + libavutil-dev, + libsdl2-dev, +Build-Depends-Indep: + android-sdk, + android-sdk-platform-23, + default-jdk, + unzip, + zip, +Rules-Requires-Root: no +Standards-Version: 4.5.0 +Homepage: https://github.com/Genymobile/scrcpy +Vcs-Git: https://salsa.debian.org/yangfl-guest/scrcpy.git +Vcs-Browser: https://salsa.debian.org/yangfl-guest/scrcpy + +Package: scrcpy +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends}, scrcpy-server, +Recommends: adb, +Description: Display and control your Android device + This application provides display and control of Android devices connected on + USB (or over TCP/IP). It does not require any root access. + . + This package contains the client (desktop) binary. + +Package: scrcpy-server +Architecture: all +Multi-Arch: foreign +Depends: ${misc:Depends}, +Description: Display and control your Android device - server binary + This application provides display and control of Android devices connected on + USB (or over TCP/IP). It does not require any root access. + . + This package contains the server (mobile) binary, which will be pushed to the + Android device. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..c45e8d78 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,38 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: scrcpy +Upstream-Contact: Romain Vimont +Source: https://github.com/Genymobile/scrcpy + +Files: * +Copyright: 2018 Genymobile + 2018-2019 Romain Vimont +License: Apache-2 + +Files: app/src/android/input.h + app/src/android/keycodes.h +Copyright: 2010 The Android Open Source Project +License: Apache-2 + +Files: debian/missing-sources/gradle/wrapper/gradle-wrapper/* +Copyright: 2007-2017 Gradle authors +License: Apache-2 + +Files: debian/* +Copyright: 2019 Yangfl +License: Apache-2 + +License: Apache-2 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + . + On Debian systems, the full text of the Apache License, Version 2.0 + can be found in the file '/usr/share/common-licenses/Apache-2.0'. diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 00000000..45052a12 --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,4 @@ +[DEFAULT] +upstream-branch = upstream +debian-branch = master +pristine-tar = True diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml new file mode 100644 index 00000000..0c22dc43 --- /dev/null +++ b/debian/gitlab-ci.yml @@ -0,0 +1,3 @@ +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/BootstrapMainStarter.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/BootstrapMainStarter.java new file mode 100644 index 00000000..68681a10 --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/BootstrapMainStarter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.wrapper; + +import java.io.Closeable; +import java.io.File; +import java.io.FilenameFilter; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +public class BootstrapMainStarter { + public void start(String[] args, File gradleHome) throws Exception { + File gradleJar = findLauncherJar(gradleHome); + if (gradleJar == null) { + throw new RuntimeException(String.format("Could not locate the Gradle launcher JAR in Gradle distribution '%s'.", gradleHome)); + } + URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent()); + Thread.currentThread().setContextClassLoader(contextClassLoader); + Class mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain"); + Method mainMethod = mainClass.getMethod("main", String[].class); + mainMethod.invoke(null, new Object[]{args}); + if (contextClassLoader instanceof Closeable) { + ((Closeable) contextClassLoader).close(); + } + } + + static File findLauncherJar(File gradleHome) { + File libDirectory = new File(gradleHome, "lib"); + if (libDirectory.exists() && libDirectory.isDirectory()) { + File[] launcherJars = libDirectory.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.matches("gradle-launcher-.*\\.jar"); + } + }); + if (launcherJars!=null && launcherJars.length==1) { + return launcherJars[0]; + } + } + return null; + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Download.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Download.java new file mode 100644 index 00000000..54cac9ce --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Download.java @@ -0,0 +1,235 @@ +/* + * Copyright 2007-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.wrapper; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; + +public class Download implements IDownload { + public static final String UNKNOWN_VERSION = "0"; + + private static final int BUFFER_SIZE = 10 * 1024; + private static final int PROGRESS_CHUNK = 1024 * 1024; + private final Logger logger; + private final String appName; + private final String appVersion; + private final DownloadProgressListener progressListener; + + public Download(Logger logger, String appName, String appVersion) { + this(logger, null, appName, appVersion); + } + + public Download(Logger logger, DownloadProgressListener progressListener, String appName, String appVersion) { + this.logger = logger; + this.appName = appName; + this.appVersion = appVersion; + this.progressListener = new DefaultDownloadProgressListener(logger, progressListener); + configureProxyAuthentication(); + } + + private void configureProxyAuthentication() { + if (System.getProperty("http.proxyUser") != null) { + Authenticator.setDefault(new ProxyAuthenticator()); + } + } + + public void download(URI address, File destination) throws Exception { + destination.getParentFile().mkdirs(); + downloadInternal(address, destination); + } + + private void downloadInternal(URI address, File destination) + throws Exception { + OutputStream out = null; + URLConnection conn; + InputStream in = null; + try { + URL url = safeUri(address).toURL(); + out = new BufferedOutputStream(new FileOutputStream(destination)); + conn = url.openConnection(); + addBasicAuthentication(address, conn); + final String userAgentValue = calculateUserAgent(); + conn.setRequestProperty("User-Agent", userAgentValue); + in = conn.getInputStream(); + byte[] buffer = new byte[BUFFER_SIZE]; + int numRead; + int totalLength = conn.getContentLength(); + long downloadedLength = 0; + long progressCounter = 0; + while ((numRead = in.read(buffer)) != -1) { + if (Thread.currentThread().isInterrupted()) { + System.out.print("interrupted"); + throw new IOException("Download was interrupted."); + } + + downloadedLength += numRead; + progressCounter += numRead; + + if (progressCounter / PROGRESS_CHUNK > 0 || downloadedLength == totalLength) { + progressCounter = progressCounter - PROGRESS_CHUNK; + progressListener.downloadStatusChanged(address, totalLength, downloadedLength); + } + + out.write(buffer, 0, numRead); + } + } finally { + logger.log(""); + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } + } + + /** + * Create a safe URI from the given one by stripping out user info. + * + * @param uri Original URI + * @return a new URI with no user info + */ + static URI safeUri(URI uri) { + try { + return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); + } catch (URISyntaxException e) { + throw new RuntimeException("Failed to parse URI", e); + } + } + + private void addBasicAuthentication(URI address, URLConnection connection) throws IOException { + String userInfo = calculateUserInfo(address); + if (userInfo == null) { + return; + } + if (!"https".equals(address.getScheme())) { + logger.log("WARNING Using HTTP Basic Authentication over an insecure connection to download the Gradle distribution. Please consider using HTTPS."); + } + connection.setRequestProperty("Authorization", "Basic " + base64Encode(userInfo)); + } + + /** + * Base64 encode user info for HTTP Basic Authentication. + * + * Try to use {@literal java.util.Base64} encoder which is available starting with Java 8. + * Fallback to {@literal javax.xml.bind.DatatypeConverter} from JAXB which is available starting with Java 6 but is not anymore in Java 9. + * Fortunately, both of these two Base64 encoders implement the right Base64 flavor, the one that does not split the output in multiple lines. + * + * @param userInfo user info + * @return Base64 encoded user info + * @throws RuntimeException if no public Base64 encoder is available on this JVM + */ + private String base64Encode(String userInfo) { + ClassLoader loader = getClass().getClassLoader(); + try { + Method getEncoderMethod = loader.loadClass("java.util.Base64").getMethod("getEncoder"); + Method encodeMethod = loader.loadClass("java.util.Base64$Encoder").getMethod("encodeToString", byte[].class); + Object encoder = getEncoderMethod.invoke(null); + return (String) encodeMethod.invoke(encoder, new Object[]{userInfo.getBytes("UTF-8")}); + } catch (Exception java7OrEarlier) { + try { + Method encodeMethod = loader.loadClass("javax.xml.bind.DatatypeConverter").getMethod("printBase64Binary", byte[].class); + return (String) encodeMethod.invoke(null, new Object[]{userInfo.getBytes("UTF-8")}); + } catch (Exception java5OrEarlier) { + throw new RuntimeException("Downloading Gradle distributions with HTTP Basic Authentication is not supported on your JVM.", java5OrEarlier); + } + } + } + + private String calculateUserInfo(URI uri) { + String username = System.getProperty("gradle.wrapperUser"); + String password = System.getProperty("gradle.wrapperPassword"); + if (username != null && password != null) { + return username + ':' + password; + } + return uri.getUserInfo(); + } + + private String calculateUserAgent() { + String javaVendor = System.getProperty("java.vendor"); + String javaVersion = System.getProperty("java.version"); + String javaVendorVersion = System.getProperty("java.vm.version"); + String osName = System.getProperty("os.name"); + String osVersion = System.getProperty("os.version"); + String osArch = System.getProperty("os.arch"); + return String.format("%s/%s (%s;%s;%s) (%s;%s;%s)", appName, appVersion, osName, osVersion, osArch, javaVendor, javaVersion, javaVendorVersion); + } + + private static class ProxyAuthenticator extends Authenticator { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication( + System.getProperty("http.proxyUser"), System.getProperty( + "http.proxyPassword", "").toCharArray()); + } + } + + private static class DefaultDownloadProgressListener implements DownloadProgressListener { + private final Logger logger; + private final DownloadProgressListener delegate; + private int previousDownloadPercent; + + public DefaultDownloadProgressListener(Logger logger, DownloadProgressListener delegate) { + this.logger = logger; + this.delegate = delegate; + this.previousDownloadPercent = 0; + } + + @Override + public void downloadStatusChanged(URI address, long contentLength, long downloaded) { + // If the total size of distribution is known, but there's no advanced progress listener, provide extra progress information + if (contentLength > 0 && delegate == null) { + appendPercentageSoFar(contentLength, downloaded); + } + + if (contentLength != downloaded) { + logger.append("."); + } + + if (delegate != null) { + delegate.downloadStatusChanged(address, contentLength, downloaded); + } + } + + private void appendPercentageSoFar(long contentLength, long downloaded) { + try { + int currentDownloadPercent = 10 * (calculateDownloadPercent(contentLength, downloaded) / 10); + if (currentDownloadPercent != 0 && previousDownloadPercent != currentDownloadPercent) { + logger.append(String.valueOf(currentDownloadPercent)).append('%'); + previousDownloadPercent = currentDownloadPercent; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private int calculateDownloadPercent(long totalLength, long downloadedLength) { + return Math.min(100, Math.max(0, (int) ((downloadedLength / (double) totalLength) * 100))); + } + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/DownloadProgressListener.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/DownloadProgressListener.java new file mode 100644 index 00000000..0b26584a --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/DownloadProgressListener.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.wrapper; + +import java.net.URI; + +public interface DownloadProgressListener { + /** + * Reports the current progress of the download + * + * @param address distribution url + * @param contentLength the content length of the distribution, or -1 if the content length is not known. + * @param downloaded the total amount of currently downloaded bytes + */ + void downloadStatusChanged(URI address, long contentLength, long downloaded); +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/ExclusiveFileAccessManager.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/ExclusiveFileAccessManager.java new file mode 100644 index 00000000..0d10f21f --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/ExclusiveFileAccessManager.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.wrapper; + +import java.io.Closeable; +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.util.concurrent.Callable; + +public class ExclusiveFileAccessManager { + + public static final String LOCK_FILE_SUFFIX = ".lck"; + + private final int timeoutMs; + private final int pollIntervalMs; + + public ExclusiveFileAccessManager(int timeoutMs, int pollIntervalMs) { + this.timeoutMs = timeoutMs; + this.pollIntervalMs = pollIntervalMs; + } + + public T access(File exclusiveFile, Callable task) throws Exception { + final File lockFile = new File(exclusiveFile.getParentFile(), exclusiveFile.getName() + LOCK_FILE_SUFFIX); + File lockFileDirectory = lockFile.getParentFile(); + if (!lockFileDirectory.mkdirs() + && (!lockFileDirectory.exists() || !lockFileDirectory.isDirectory())) { + throw new RuntimeException("Could not create parent directory for lock file " + lockFile.getAbsolutePath()); + } + RandomAccessFile randomAccessFile = null; + FileChannel channel = null; + try { + + long expiry = getTimeMillis() + timeoutMs; + FileLock lock = null; + + while (lock == null && getTimeMillis() < expiry) { + randomAccessFile = new RandomAccessFile(lockFile, "rw"); + channel = randomAccessFile.getChannel(); + lock = channel.tryLock(); + + if (lock == null) { + maybeCloseQuietly(channel); + maybeCloseQuietly(randomAccessFile); + Thread.sleep(pollIntervalMs); + } + } + + if (lock == null) { + throw new RuntimeException("Timeout of " + timeoutMs + " reached waiting for exclusive access to file: " + exclusiveFile.getAbsolutePath()); + } + + try { + return task.call(); + } finally { + lock.release(); + + maybeCloseQuietly(channel); + channel = null; + maybeCloseQuietly(randomAccessFile); + randomAccessFile = null; + } + } finally { + maybeCloseQuietly(channel); + maybeCloseQuietly(randomAccessFile); + } + } + + private long getTimeMillis() { + return System.nanoTime() / (1000L * 1000L); + } + + private static void maybeCloseQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception ignore) { + // + } + } + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/GradleUserHomeLookup.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/GradleUserHomeLookup.java new file mode 100644 index 00000000..99290244 --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/GradleUserHomeLookup.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.wrapper; + +import java.io.File; + +public class GradleUserHomeLookup { + public static final String DEFAULT_GRADLE_USER_HOME = System.getProperty("user.home") + "/.gradle"; + public static final String GRADLE_USER_HOME_PROPERTY_KEY = "gradle.user.home"; + public static final String GRADLE_USER_HOME_ENV_KEY = "GRADLE_USER_HOME"; + + public static File gradleUserHome() { + String gradleUserHome; + if ((gradleUserHome = System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY)) != null) { + return new File(gradleUserHome); + } + if ((gradleUserHome = System.getenv(GRADLE_USER_HOME_ENV_KEY)) != null) { + return new File(gradleUserHome); + } + return new File(DEFAULT_GRADLE_USER_HOME); + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/GradleWrapperMain.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/GradleWrapperMain.java new file mode 100644 index 00000000..fe562302 --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/GradleWrapperMain.java @@ -0,0 +1,109 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.wrapper; + +import org.gradle.cli.CommandLineParser; +import org.gradle.cli.ParsedCommandLine; +import org.gradle.cli.SystemPropertiesCommandLineConverter; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Properties; + +import static org.gradle.wrapper.Download.UNKNOWN_VERSION; + +public class GradleWrapperMain { + public static final String GRADLE_USER_HOME_OPTION = "g"; + public static final String GRADLE_USER_HOME_DETAILED_OPTION = "gradle-user-home"; + public static final String GRADLE_QUIET_OPTION = "q"; + public static final String GRADLE_QUIET_DETAILED_OPTION = "quiet"; + + public static void main(String[] args) throws Exception { + File wrapperJar = wrapperJar(); + File propertiesFile = wrapperProperties(wrapperJar); + File rootDir = rootDir(wrapperJar); + + CommandLineParser parser = new CommandLineParser(); + parser.allowUnknownOptions(); + parser.option(GRADLE_USER_HOME_OPTION, GRADLE_USER_HOME_DETAILED_OPTION).hasArgument(); + parser.option(GRADLE_QUIET_OPTION, GRADLE_QUIET_DETAILED_OPTION); + + SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter(); + converter.configure(parser); + + ParsedCommandLine options = parser.parse(args); + + Properties systemProperties = System.getProperties(); + systemProperties.putAll(converter.convert(options, new HashMap())); + + File gradleUserHome = gradleUserHome(options); + + addSystemProperties(gradleUserHome, rootDir); + + Logger logger = logger(options); + + WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile); + wrapperExecutor.execute( + args, + new Install(logger, new Download(logger, "gradlew", UNKNOWN_VERSION), new PathAssembler(gradleUserHome)), + new BootstrapMainStarter()); + } + + private static void addSystemProperties(File gradleHome, File rootDir) { + System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File(gradleHome, "gradle.properties"))); + System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File(rootDir, "gradle.properties"))); + } + + private static File rootDir(File wrapperJar) { + return wrapperJar.getParentFile().getParentFile().getParentFile(); + } + + private static File wrapperProperties(File wrapperJar) { + return new File(wrapperJar.getParent(), wrapperJar.getName().replaceFirst("\\.jar$", ".properties")); + } + + private static File wrapperJar() { + URI location; + try { + location = GradleWrapperMain.class.getProtectionDomain().getCodeSource().getLocation().toURI(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + if (!location.getScheme().equals("file")) { + throw new RuntimeException(String.format("Cannot determine classpath for wrapper Jar from codebase '%s'.", location)); + } + try { + return Paths.get(location).toFile(); + } catch (NoClassDefFoundError e) { + return new File(location.getPath()); + } + } + + private static File gradleUserHome(ParsedCommandLine options) { + if (options.hasOption(GRADLE_USER_HOME_OPTION)) { + return new File(options.option(GRADLE_USER_HOME_OPTION).getValue()); + } + return GradleUserHomeLookup.gradleUserHome(); + } + + private static Logger logger(ParsedCommandLine options) { + return new Logger(options.hasOption(GRADLE_QUIET_OPTION)); + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/IDownload.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/IDownload.java new file mode 100644 index 00000000..078490c9 --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/IDownload.java @@ -0,0 +1,23 @@ +/* + * Copyright 2007-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.wrapper; + +import java.io.File; +import java.net.URI; + +public interface IDownload { + void download(URI address, File destination) throws Exception; +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Install.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Install.java new file mode 100644 index 00000000..e02e425a --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Install.java @@ -0,0 +1,297 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.wrapper; + +import java.io.*; +import java.net.URI; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.security.MessageDigest; + +public class Install { + public static final String DEFAULT_DISTRIBUTION_PATH = "wrapper/dists"; + private final Logger logger; + private final IDownload download; + private final PathAssembler pathAssembler; + private final ExclusiveFileAccessManager exclusiveFileAccessManager = new ExclusiveFileAccessManager(120000, 200); + + public Install(Logger logger, IDownload download, PathAssembler pathAssembler) { + this.logger = logger; + this.download = download; + this.pathAssembler = pathAssembler; + } + + public File createDist(final WrapperConfiguration configuration) throws Exception { + final URI distributionUrl = configuration.getDistribution(); + final String distributionSha256Sum = configuration.getDistributionSha256Sum(); + + final PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution(configuration); + final File distDir = localDistribution.getDistributionDir(); + final File localZipFile = localDistribution.getZipFile(); + + return exclusiveFileAccessManager.access(localZipFile, new Callable() { + public File call() throws Exception { + final File markerFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".ok"); + if (distDir.isDirectory() && markerFile.isFile()) { + InstallCheck installCheck = verifyDistributionRoot(distDir, distDir.getAbsolutePath()); + if (installCheck.isVerified()) { + return installCheck.gradleHome; + } + // Distribution is invalid. Try to reinstall. + System.err.println(installCheck.failureMessage); + markerFile.delete(); + } + + boolean needsDownload = !localZipFile.isFile(); + URI safeDistributionUrl = Download.safeUri(distributionUrl); + + if (needsDownload) { + File tmpZipFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".part"); + tmpZipFile.delete(); + logger.log("Downloading " + safeDistributionUrl); + download.download(distributionUrl, tmpZipFile); + tmpZipFile.renameTo(localZipFile); + } + + List topLevelDirs = listDirs(distDir); + for (File dir : topLevelDirs) { + logger.log("Deleting directory " + dir.getAbsolutePath()); + deleteDir(dir); + } + + verifyDownloadChecksum(configuration.getDistribution().toString(), localZipFile, distributionSha256Sum); + + try { + unzip(localZipFile, distDir); + } catch (IOException e) { + logger.log("Could not unzip " + localZipFile.getAbsolutePath() + " to " + distDir.getAbsolutePath() + "."); + logger.log("Reason: " + e.getMessage()); + throw e; + } + + InstallCheck installCheck = verifyDistributionRoot(distDir, safeDistributionUrl.toString()); + if (installCheck.isVerified()) { + setExecutablePermissions(installCheck.gradleHome); + markerFile.createNewFile(); + return installCheck.gradleHome; + } + // Distribution couldn't be installed. + throw new RuntimeException(installCheck.failureMessage); + } + }); + } + + private String calculateSha256Sum(File file) + throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + InputStream fis = new FileInputStream(file); + try { + int n = 0; + byte[] buffer = new byte[4096]; + while (n != -1) { + n = fis.read(buffer); + if (n > 0) { + md.update(buffer, 0, n); + } + } + } finally { + fis.close(); + } + + byte byteData[] = md.digest(); + + StringBuffer hexString = new StringBuffer(); + for (int i = 0; i < byteData.length; i++) { + String hex = Integer.toHexString(0xff & byteData[i]); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + + return hexString.toString(); + } + + private InstallCheck verifyDistributionRoot(File distDir, String distributionDescription) + throws Exception { + List dirs = listDirs(distDir); + if (dirs.isEmpty()) { + return InstallCheck.failure(String.format("Gradle distribution '%s' does not contain any directories. Expected to find exactly 1 directory.", distributionDescription)); + } + if (dirs.size() != 1) { + return InstallCheck.failure(String.format("Gradle distribution '%s' contains too many directories. Expected to find exactly 1 directory.", distributionDescription)); + } + + File gradleHome = dirs.get(0); + if (BootstrapMainStarter.findLauncherJar(gradleHome) == null) { + return InstallCheck.failure(String.format("Gradle distribution '%s' does not appear to contain a Gradle distribution.", distributionDescription)); + } + return InstallCheck.success(gradleHome); + } + + private void verifyDownloadChecksum(String sourceUrl, File localZipFile, String expectedSum) throws Exception { + if (expectedSum != null) { + // if a SHA-256 hash sum has been defined in gradle-wrapper.properties, verify it here + String actualSum = calculateSha256Sum(localZipFile); + if (!expectedSum.equals(actualSum)) { + localZipFile.delete(); + String message = String.format("Verification of Gradle distribution failed!%n" + + "%n" + + "Your Gradle distribution may have been tampered with.%n" + + "Confirm that the 'distributionSha256Sum' property in your gradle-wrapper.properties file is correct and you are downloading the wrapper from a trusted source.%n" + + "%n" + + " Distribution Url: %s%n" + + "Download Location: %s%n" + + "Expected checksum: '%s'%n" + + " Actual checksum: '%s'%n", + sourceUrl, localZipFile.getAbsolutePath(), expectedSum, actualSum + ); + throw new RuntimeException(message); + } + } + } + + private List listDirs(File distDir) { + List dirs = new ArrayList(); + if (distDir.exists()) { + File[] files = distDir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + dirs.add(file); + } + } + } + } + return dirs; + } + + private void setExecutablePermissions(File gradleHome) { + if (isWindows()) { + return; + } + File gradleCommand = new File(gradleHome, "bin/gradle"); + String errorMessage = null; + try { + ProcessBuilder pb = new ProcessBuilder("chmod", "755", gradleCommand.getCanonicalPath()); + Process p = pb.start(); + if (p.waitFor() != 0) { + BufferedReader is = new BufferedReader(new InputStreamReader(p.getInputStream())); + Formatter stdout = new Formatter(); + String line; + while ((line = is.readLine()) != null) { + stdout.format("%s%n", line); + } + errorMessage = stdout.toString(); + } + } catch (IOException e) { + errorMessage = e.getMessage(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + errorMessage = e.getMessage(); + } + if (errorMessage != null) { + logger.log("Could not set executable permissions for: " + gradleCommand.getAbsolutePath()); + } + } + + private boolean isWindows() { + String osName = System.getProperty("os.name").toLowerCase(Locale.US); + if (osName.indexOf("windows") > -1) { + return true; + } + return false; + } + + private boolean deleteDir(File dir) { + if (dir.isDirectory()) { + String[] children = dir.list(); + for (int i = 0; i < children.length; i++) { + boolean success = deleteDir(new File(dir, children[i])); + if (!success) { + return false; + } + } + } + + // The directory is now empty so delete it + return dir.delete(); + } + + private void unzip(File zip, File dest) throws IOException { + Enumeration entries; + ZipFile zipFile = new ZipFile(zip); + try { + entries = zipFile.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = (ZipEntry) entries.nextElement(); + + if (entry.isDirectory()) { + (new File(dest, entry.getName())).mkdirs(); + continue; + } + + OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(new File(dest, entry.getName()))); + try { + copyInputStream(zipFile.getInputStream(entry), outputStream); + } finally { + outputStream.close(); + } + } + } finally { + zipFile.close(); + } + } + + private void copyInputStream(InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[1024]; + int len; + + while ((len = in.read(buffer)) >= 0) { + out.write(buffer, 0, len); + } + + in.close(); + out.close(); + } + + private static class InstallCheck { + private final File gradleHome; + private final String failureMessage; + + private static InstallCheck failure(String message) { + return new InstallCheck(null, message); + } + + private static InstallCheck success(File gradleHome) { + return new InstallCheck(gradleHome, null); + } + + private InstallCheck(File gradleHome, String failureMessage) { + this.gradleHome = gradleHome; + this.failureMessage = failureMessage; + } + + private boolean isVerified() { + return gradleHome != null; + } + } + +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Logger.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Logger.java new file mode 100644 index 00000000..82bbe90d --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/Logger.java @@ -0,0 +1,52 @@ +/* + * Copyright 2007-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.wrapper; + +public class Logger implements Appendable { + + private final boolean quiet; + + public Logger(boolean quiet) { + this.quiet = quiet; + } + + public void log(String message) { + if (!quiet) { + System.out.println(message); + } + } + + public Appendable append(CharSequence csq) { + if (!quiet) { + System.out.append(csq); + } + return this; + } + + public Appendable append(CharSequence csq, int start, int end) { + if (!quiet) { + System.out.append(csq, start, end); + } + return this; + } + + public Appendable append(char c) { + if(!quiet) { + System.out.append(c); + } + return this; + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/PathAssembler.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/PathAssembler.java new file mode 100644 index 00000000..7b0ea6cf --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/PathAssembler.java @@ -0,0 +1,124 @@ +/* + * Copyright 2007-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.wrapper; + +import java.io.File; +import java.math.BigInteger; +import java.net.URI; +import java.security.MessageDigest; + +public class PathAssembler { + public static final String GRADLE_USER_HOME_STRING = "GRADLE_USER_HOME"; + public static final String PROJECT_STRING = "PROJECT"; + + private File gradleUserHome; + + public PathAssembler() { + } + + public PathAssembler(File gradleUserHome) { + this.gradleUserHome = gradleUserHome; + } + + /** + * Determines the local locations for the distribution to use given the supplied configuration. + */ + public LocalDistribution getDistribution(WrapperConfiguration configuration) { + String baseName = getDistName(configuration.getDistribution()); + String distName = removeExtension(baseName); + String rootDirName = rootDirName(distName, configuration); + File distDir = new File(getBaseDir(configuration.getDistributionBase()), configuration.getDistributionPath() + "/" + rootDirName); + File distZip = new File(getBaseDir(configuration.getZipBase()), configuration.getZipPath() + "/" + rootDirName + "/" + baseName); + return new LocalDistribution(distDir, distZip); + } + + private String rootDirName(String distName, WrapperConfiguration configuration) { + String urlHash = getHash(Download.safeUri(configuration.getDistribution()).toString()); + return distName + "/" + urlHash; + } + + /** + * This method computes a hash of the provided {@code string}. + *

+ * The algorithm in use by this method is as follows: + *

    + *
  1. Compute the MD5 value of {@code string}.
  2. + *
  3. Truncate leading zeros (i.e., treat the MD5 value as a number).
  4. + *
  5. Convert to base 36 (the characters {@code 0-9a-z}).
  6. + *
+ */ + private String getHash(String string) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + byte[] bytes = string.getBytes(); + messageDigest.update(bytes); + return new BigInteger(1, messageDigest.digest()).toString(36); + } catch (Exception e) { + throw new RuntimeException("Could not hash input string.", e); + } + } + + private String removeExtension(String name) { + int p = name.lastIndexOf("."); + if (p < 0) { + return name; + } + return name.substring(0, p); + } + + private String getDistName(URI distUrl) { + String path = distUrl.getPath(); + int p = path.lastIndexOf("/"); + if (p < 0) { + return path; + } + return path.substring(p + 1); + } + + private File getBaseDir(String base) { + if (base.equals(GRADLE_USER_HOME_STRING)) { + return gradleUserHome; + } else if (base.equals(PROJECT_STRING)) { + return new File(System.getProperty("user.dir")); + } else { + throw new RuntimeException("Base: " + base + " is unknown"); + } + } + + public static class LocalDistribution { + private final File distZip; + private final File distDir; + + public LocalDistribution(File distDir, File distZip) { + this.distDir = distDir; + this.distZip = distZip; + } + + /** + * Returns the location to install the distribution into. + */ + public File getDistributionDir() { + return distDir; + } + + /** + * Returns the location to install the distribution ZIP file to. + */ + public File getZipFile() { + return distZip; + } + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/SystemPropertiesHandler.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/SystemPropertiesHandler.java new file mode 100644 index 00000000..b4dede44 --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/SystemPropertiesHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.wrapper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class SystemPropertiesHandler { + static final String SYSTEM_PROP_PREFIX = "systemProp."; + + public static Map getSystemProperties(File propertiesFile) { + Map propertyMap = new HashMap(); + if (!propertiesFile.isFile()) { + return propertyMap; + } + Properties properties = new Properties(); + try { + FileInputStream inStream = new FileInputStream(propertiesFile); + try { + properties.load(inStream); + } finally { + inStream.close(); + } + } catch (IOException e) { + throw new RuntimeException("Error when loading properties file=" + propertiesFile, e); + } + + for (Object argument : properties.keySet()) { + if (argument.toString().startsWith(SYSTEM_PROP_PREFIX)) { + String key = argument.toString().substring(SYSTEM_PROP_PREFIX.length()); + if (key.length() > 0) { + propertyMap.put(key, properties.get(argument).toString()); + } + } + } + return propertyMap; + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/WrapperConfiguration.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/WrapperConfiguration.java new file mode 100644 index 00000000..87f6c438 --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/WrapperConfiguration.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.wrapper; + +import java.net.URI; + +public class WrapperConfiguration { + private URI distribution; + private String distributionBase = PathAssembler.GRADLE_USER_HOME_STRING; + private String distributionPath = Install.DEFAULT_DISTRIBUTION_PATH; + private String distributionSha256Sum; + private String zipBase = PathAssembler.GRADLE_USER_HOME_STRING; + private String zipPath = Install.DEFAULT_DISTRIBUTION_PATH; + + public URI getDistribution() { + return distribution; + } + + public void setDistribution(URI distribution) { + this.distribution = distribution; + } + + public String getDistributionBase() { + return distributionBase; + } + + public void setDistributionBase(String distributionBase) { + this.distributionBase = distributionBase; + } + + public String getDistributionPath() { + return distributionPath; + } + + public void setDistributionPath(String distributionPath) { + this.distributionPath = distributionPath; + } + + public String getDistributionSha256Sum() { + return distributionSha256Sum; + } + + public void setDistributionSha256Sum(String distributionSha256Sum) { + this.distributionSha256Sum = distributionSha256Sum; + } + + public String getZipBase() { + return zipBase; + } + + public void setZipBase(String zipBase) { + this.zipBase = zipBase; + } + + public String getZipPath() { + return zipPath; + } + + public void setZipPath(String zipPath) { + this.zipPath = zipPath; + } +} diff --git a/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/WrapperExecutor.java b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/WrapperExecutor.java new file mode 100644 index 00000000..6160b7f8 --- /dev/null +++ b/debian/missing-sources/gradle/wrapper/gradle-wrapper/java/org/gradle/wrapper/WrapperExecutor.java @@ -0,0 +1,138 @@ +/* + * Copyright 2007-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.wrapper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Properties; + +public class WrapperExecutor { + public static final String DISTRIBUTION_URL_PROPERTY = "distributionUrl"; + public static final String DISTRIBUTION_BASE_PROPERTY = "distributionBase"; + public static final String DISTRIBUTION_PATH_PROPERTY = "distributionPath"; + public static final String DISTRIBUTION_SHA_256_SUM = "distributionSha256Sum"; + public static final String ZIP_STORE_BASE_PROPERTY = "zipStoreBase"; + public static final String ZIP_STORE_PATH_PROPERTY = "zipStorePath"; + private final Properties properties; + private final File propertiesFile; + private final WrapperConfiguration config = new WrapperConfiguration(); + + public static WrapperExecutor forProjectDirectory(File projectDir) { + return new WrapperExecutor(new File(projectDir, "gradle/wrapper/gradle-wrapper.properties"), new Properties()); + } + + public static WrapperExecutor forWrapperPropertiesFile(File propertiesFile) { + if (!propertiesFile.exists()) { + throw new RuntimeException(String.format("Wrapper properties file '%s' does not exist.", propertiesFile)); + } + return new WrapperExecutor(propertiesFile, new Properties()); + } + + WrapperExecutor(File propertiesFile, Properties properties) { + this.properties = properties; + this.propertiesFile = propertiesFile; + if (propertiesFile.exists()) { + try { + loadProperties(propertiesFile, properties); + config.setDistribution(prepareDistributionUri()); + config.setDistributionBase(getProperty(DISTRIBUTION_BASE_PROPERTY, config.getDistributionBase())); + config.setDistributionPath(getProperty(DISTRIBUTION_PATH_PROPERTY, config.getDistributionPath())); + config.setDistributionSha256Sum(getProperty(DISTRIBUTION_SHA_256_SUM, config.getDistributionSha256Sum(), false)); + config.setZipBase(getProperty(ZIP_STORE_BASE_PROPERTY, config.getZipBase())); + config.setZipPath(getProperty(ZIP_STORE_PATH_PROPERTY, config.getZipPath())); + } catch (Exception e) { + throw new RuntimeException(String.format("Could not load wrapper properties from '%s'.", propertiesFile), e); + } + } + } + + private URI prepareDistributionUri() throws URISyntaxException { + URI source = readDistroUrl(); + if (source.getScheme() == null) { + //no scheme means someone passed a relative url. In our context only file relative urls make sense. + return new File(propertiesFile.getParentFile(), source.getSchemeSpecificPart()).toURI(); + } else { + return source; + } + } + + private URI readDistroUrl() throws URISyntaxException { + if (properties.getProperty(DISTRIBUTION_URL_PROPERTY) == null) { + reportMissingProperty(DISTRIBUTION_URL_PROPERTY); + } + return new URI(getProperty(DISTRIBUTION_URL_PROPERTY)); + } + + private static void loadProperties(File propertiesFile, Properties properties) throws IOException { + InputStream inStream = new FileInputStream(propertiesFile); + try { + properties.load(inStream); + } finally { + inStream.close(); + } + } + + /** + * Returns the distribution which this wrapper will use. Returns null if no wrapper meta-data was found in the specified project directory. + */ + public URI getDistribution() { + return config.getDistribution(); + } + + /** + * Returns the configuration for this wrapper. + */ + public WrapperConfiguration getConfiguration() { + return config; + } + + public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception { + File gradleHome = install.createDist(config); + bootstrapMainStarter.start(args, gradleHome); + } + + private String getProperty(String propertyName) { + return getProperty(propertyName, null, true); + } + + private String getProperty(String propertyName, String defaultValue) { + return getProperty(propertyName, defaultValue, true); + } + + private String getProperty(String propertyName, String defaultValue, boolean required) { + String value = properties.getProperty(propertyName); + if (value != null) { + return value; + } + if (defaultValue != null) { + return defaultValue; + } + if (required) { + return reportMissingProperty(propertyName); + } else { + return null; + } + } + + private String reportMissingProperty(String propertyName) { + throw new RuntimeException(String.format( + "No value with key '%s' specified in wrapper properties file '%s'.", propertyName, propertiesFile)); + } +} diff --git a/debian/patches/0001-No-Android-Q.patch b/debian/patches/0001-No-Android-Q.patch new file mode 100644 index 00000000..3bdd5ca7 --- /dev/null +++ b/debian/patches/0001-No-Android-Q.patch @@ -0,0 +1,108 @@ +diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java +index 9100a9d..572a1b2 100644 +--- a/server/src/main/java/com/genymobile/scrcpy/Controller.java ++++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java +@@ -262,8 +262,8 @@ public class Controller { + } + + // On Android >= 7, also press the PASTE key if requested +- if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) { +- device.injectKeycode(KeyEvent.KEYCODE_PASTE); ++ if (paste && Build.VERSION.SDK_INT >= 24 && device.supportsInputEvents()) { ++ device.injectKeycode(279); + } + + return ok; +diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java +index f23dd05..6be0652 100644 +--- a/server/src/main/java/com/genymobile/scrcpy/Device.java ++++ b/server/src/main/java/com/genymobile/scrcpy/Device.java +@@ -110,7 +110,7 @@ public final class Device { + } + + // main display or any display on Android >= Q +- supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; ++ supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= 29; + if (!supportsInputEvents) { + Ln.w("Input events are not supported for secondary displays before Android 10"); + } +diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +index e25b6e9..9566de6 100644 +--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java ++++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java +@@ -22,7 +22,7 @@ public class ClipboardManager { + + private Method getGetPrimaryClipMethod() throws NoSuchMethodException { + if (getPrimaryClipMethod == null) { +- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { ++ if (Build.VERSION.SDK_INT < 29) { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class); + } else { + getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class); +@@ -33,7 +33,7 @@ public class ClipboardManager { + + private Method getSetPrimaryClipMethod() throws NoSuchMethodException { + if (setPrimaryClipMethod == null) { +- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { ++ if (Build.VERSION.SDK_INT < 29) { + setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class); + } else { + setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class); +@@ -43,7 +43,7 @@ public class ClipboardManager { + } + + private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException, IllegalAccessException { +- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { ++ if (Build.VERSION.SDK_INT < 29) { + return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME); + } + return (ClipData) method.invoke(manager, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); +@@ -51,7 +51,7 @@ public class ClipboardManager { + + private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData) + throws InvocationTargetException, IllegalAccessException { +- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { ++ if (Build.VERSION.SDK_INT < 29) { + method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME); + } else { + method.invoke(manager, clipData, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); +@@ -86,7 +86,7 @@ public class ClipboardManager { + + private static void addPrimaryClipChangedListener(Method method, IInterface manager, IOnPrimaryClipChangedListener listener) + throws InvocationTargetException, IllegalAccessException { +- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { ++ if (Build.VERSION.SDK_INT < 29) { + method.invoke(manager, listener, ServiceManager.PACKAGE_NAME); + } else { + method.invoke(manager, listener, ServiceManager.PACKAGE_NAME, ServiceManager.USER_ID); +@@ -95,7 +95,7 @@ public class ClipboardManager { + + private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException { + if (addPrimaryClipChangedListener == null) { +- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { ++ if (Build.VERSION.SDK_INT < 29) { + addPrimaryClipChangedListener = manager.getClass() + .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class); + } else { +diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +index 8fbb860..de09834 100644 +--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java ++++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +@@ -88,7 +88,7 @@ public final class SurfaceControl { + if (getBuiltInDisplayMethod == null) { + // the method signature has changed in Android Q + // +- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { ++ if (Build.VERSION.SDK_INT < 29) { + getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class); + } else { + getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken"); +@@ -101,7 +101,7 @@ public final class SurfaceControl { + + try { + Method method = getGetBuiltInDisplayMethod(); +- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { ++ if (Build.VERSION.SDK_INT < 29) { + // call getBuiltInDisplay(0) + return (IBinder) method.invoke(null, 0); + } diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 00000000..1a129b92 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +0001-No-Android-Q.patch diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..c80b4d38 --- /dev/null +++ b/debian/rules @@ -0,0 +1,49 @@ +#!/usr/bin/make -f +#export DH_VERBOSE = 1 + + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + +export ANDROID_HOME=/usr/lib/android-sdk +export JAVA_HOME=/usr/lib/jvm/default-java +export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8 + +include /usr/share/dpkg/pkg-info.mk + + +%: + dh $@ + +build: + dh build-arch + dh build-indep --buildsystem=makefile + +build-arch: + dh build-arch + +build-indep: + dh build-indep --buildsystem=makefile + +clean: + dh clean + +override_dh_auto_clean: + dh_auto_clean + rm -rf build_manual + +override_dh_auto_configure-arch: + dh_auto_configure -a -- -Dcompile_server=false + +override_dh_auto_build-indep: + ANDROID_PLATFORM=23 ANDROID_BUILD_TOOLS=debian server/build_without_gradle.sh + # dirty hack for reproducible jar build + cd build_manual; \ + unzip scrcpy-server; \ + touch -t $$(date --date="@${SOURCE_DATE_EPOCH}" +%Y%m%d%H%M.%S) META-INF META-INF/MANIFEST.MF classes.dex; \ + chmod 755 META-INF; \ + chmod 644 META-INF/MANIFEST.MF classes.dex; \ + TZ=UTC zip -X scrcpy-server.zip META-INF/MANIFEST.MF classes.dex; \ + mv scrcpy-server.zip scrcpy-server diff --git a/debian/scrcpy-server.install b/debian/scrcpy-server.install new file mode 100644 index 00000000..c943becf --- /dev/null +++ b/debian/scrcpy-server.install @@ -0,0 +1 @@ +build_manual/scrcpy-server usr/share/scrcpy diff --git a/debian/scrcpy.install b/debian/scrcpy.install new file mode 100644 index 00000000..a2c0e00f --- /dev/null +++ b/debian/scrcpy.install @@ -0,0 +1,2 @@ +usr/bin/scrcpy +usr/share/man/ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/tests/control b/debian/tests/control new file mode 100644 index 00000000..b9bc7690 --- /dev/null +++ b/debian/tests/control @@ -0,0 +1,2 @@ +Tests: unittest.sh +Depends: @builddeps@ diff --git a/debian/tests/unittest.sh b/debian/tests/unittest.sh new file mode 100755 index 00000000..57fc3c7e --- /dev/null +++ b/debian/tests/unittest.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +mkdir build +cd build +meson .. --wrap-mode=nodownload -Dcompile_server=false +ninja test +rm -rf build diff --git a/debian/upstream/metadata b/debian/upstream/metadata new file mode 100644 index 00000000..22b9456f --- /dev/null +++ b/debian/upstream/metadata @@ -0,0 +1,4 @@ +Bug-Submit: https://github.com/Genymobile/scrcpy/issues +Repository: https://github.com/Genymobile/scrcpy.git +Repository-Browse: https://github.com/Genymobile/scrcpy +Screenshots: https://raw.githubusercontent.com/Genymobile/scrcpy/master/assets/screenshot-debian-600.jpg diff --git a/debian/watch b/debian/watch new file mode 100644 index 00000000..8ce4a0bd --- /dev/null +++ b/debian/watch @@ -0,0 +1,4 @@ +version=4 +opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%scrcpy-$1.tar.gz%" \ + https://github.com/Genymobile/scrcpy/tags \ + (?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdate