-
-
Save tprochazka/09bc1fdf6da0de7ad0830c911d10028d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.android.annotations.NonNull | |
import com.android.build.gradle.api.ApkVariantOutput | |
import com.android.build.gradle.api.ApplicationVariant | |
import com.android.build.gradle.internal.scope.TaskConfigAction | |
import com.avast.gradle.buildplugin.Config | |
import org.gradle.api.DefaultTask | |
import org.gradle.api.Project | |
import org.gradle.api.Task | |
import org.gradle.api.tasks.InputFile | |
import org.gradle.api.tasks.OutputFile | |
import org.gradle.api.tasks.TaskAction | |
import java.security.SecureRandom | |
/** | |
* Upload APK to remote server via SCP a run remote script via SSH. | |
* | |
* @author Tomáš Procházka (prochazka) | |
*/ | |
class RemoteSignApkTask extends DefaultTask { | |
private static final String SSH_CMD = "ssh" | |
private static final String SCP_CMD = "scp" | |
@InputFile | |
File inputFile | |
@OutputFile | |
File outputFile | |
ApplicationVariant applicationVariant | |
@TaskAction | |
void sign() { | |
def config = getConfig(project) | |
if (config == null) { | |
return | |
} | |
String sshCmd = config.sshCmd | |
String scpCmd = config.scpCmd | |
project.getLogger().lifecycle(" Remote signing... ") | |
if (inputFile == outputFile) { | |
// rename original input file if is it the same as output one | |
// it is needed if we want to keep same output name for artifact before and after sign | |
File unsignedFile = new File(inputFile.parent, inputFile.name.replace(".apk", ",unsigned.apk")) | |
inputFile.renameTo(unsignedFile) | |
inputFile = unsignedFile | |
} | |
// Generate a random string to create a unique apk names | |
SecureRandom random = new SecureRandom() | |
String uniqueBuildTag = new BigInteger(130, random).toString(32) | |
// Construct APK names | |
String srcApk = this.inputFile.getPath().replaceFirst("[A-Z]:", "") | |
String resultApk = this.outputFile.getPath().replaceFirst("[A-Z]:", "") | |
//(Note: replace is special hack for SCP on windows which doesn't allow c: in the path) | |
String dstApk = uniqueBuildTag + "_unsigned.apk" | |
String signedApk = uniqueBuildTag + "_signed.apk" | |
boolean signV2 = true // we are using just v2 already | |
// Construct command lists | |
String keyName = SignConfigurator.getRemoteSignKeyName(this.applicationVariant, project.android.getDefaultConfig()) | |
String signingScript = "apksign.sh" | |
String[] uploadApkCmd = [scpCmd, srcApk, config.signHost + ":" + dstApk] | |
String[] signApkCmd = [sshCmd, config.signHost, "sudo $signingScript -k $keyName < " + dstApk + " > " + signedApk] | |
String[] downloadApkCmd = [scpCmd, config.signHost + ":" + signedApk, resultApk] | |
String[] cleanApkCmd = [sshCmd, config.signHost, "rm " + dstApk + "; rm " + signedApk] | |
this.callCommandLine(uploadApkCmd, "Uploading APK") | |
this.callCommandLine(signApkCmd, "Signing APK with '$keyName' certificate") | |
this.callCommandLine(downloadApkCmd, "Downloading APK") | |
this.callCommandLine(cleanApkCmd, "Cleanup") | |
} | |
static boolean checkConfig(Project project) { | |
Map config = getConfig(project) | |
if (config.signHost == null) { | |
println "REMOTE_SIGNING_USER_AND_HOST not configured. Skipping signing." | |
} | |
return config.signHost != null | |
} | |
private static Map getConfig(Project project) { | |
// from the legacy reason we will still use this env variables | |
String sshCmd = ConfigProvider.get(project, "AVAST_ANDROID_BUILD_SSH_CMD", SSH_CMD) | |
String scpCmd = ConfigProvider.get(project, "AVAST_ANDROID_BUILD_SCP_CMD", SCP_CMD) | |
String signHost = ConfigProvider.get(project, "REMOTE_SIGNING_USER_AND_HOST", Config.SIGNER_URL) | |
return ["sshCmd": sshCmd, "scpCmd": scpCmd, "signHost": signHost] | |
} | |
/** | |
* Call a command line | |
* @param line A list of strings forming a command line | |
* @param tag A string which will be displayed in a log | |
*/ | |
private int callCommandLine(String[] line, String tag) { | |
logger.info("Signer CMD: " + line.join(" ")) | |
def command = Runtime.getRuntime().exec(line.join(" ")) | |
def result = command.waitFor() | |
if (result > 0) { | |
project.getLogger().error(command.errorStream.text.trim()) | |
project.getLogger().lifecycle(" " + tag + " failed with " + command.exitValue()) | |
} else { | |
project.getLogger().lifecycle(" " + tag + "... Success") | |
} | |
return command.exitValue() | |
} | |
/** | |
* Class responsible for sign task configuration | |
*/ | |
static class ConfigAction implements TaskConfigAction<RemoteSignApkTask> { | |
private final ApkVariantOutput output | |
private final ApplicationVariant variant | |
@NonNull | |
@Override | |
String getName() { | |
return output.getTaskName("RemoteSignApkTask") | |
} | |
@NonNull | |
@Override | |
Class<RemoteSignApkTask> getType() { | |
return RemoteSignApkTask.class | |
} | |
ConfigAction(ApplicationVariant variant, ApkVariantOutput output) { | |
this.variant = variant | |
this.output = output | |
} | |
@Override | |
@TaskAction | |
void execute(@NonNull RemoteSignApkTask signTask) { | |
File assembleOutputFile = output.outputFile | |
signTask.outputFile = new File(assembleOutputFile.parent, assembleOutputFile.name.replaceFirst("[,-]unsigned", "")) | |
signTask.inputFile = signTask.outputFile | |
// hack to keep proper final name also when assemble task will be skipped | |
output.outputFileName = signTask.outputFile.getName() | |
signTask.applicationVariant = variant | |
// setup task dependencies | |
signTask.dependsOn output.packageApplication | |
output.assemble.dependsOn signTask | |
variant.outputsAreSigned = true | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment