/* * Copyright 2000-2012 JetBrains s.r.o. * * 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 jetbrains.buildServer.agent.android; import jetbrains.buildServer.agent.AgentRunningBuild; import jetbrains.buildServer.agent.BuildFinishedStatus; import jetbrains.buildServer.agent.BuildProgressLogger; import jetbrains.buildServer.agent.BuildRunnerContext; import jetbrains.buildServer.agent.android.tools.AaptTool; import jetbrains.buildServer.agent.android.tools.ApkUtil; import jetbrains.buildServer.util.FileUtil; import org.jetbrains.annotations.NotNull; import java.io.File; import static jetbrains.buildServer.runner.android.AndroidRunnerConstants.*; public class AndroidBuildProcess extends FutureBasedBuildProcess { @NotNull private static final String LINE_TERMINATORS_REGEX = "\\r|\\n|\\r\\n|\\u0085|\\u2028|\\u2029"; @NotNull private final AgentRunningBuild myBuild; @NotNull private final BuildRunnerContext myContext; @NotNull private final AndroidEnvironment myEnvironment; public AndroidBuildProcess(@NotNull final AgentRunningBuild build, @NotNull final BuildRunnerContext context) { myBuild = build; myContext = context; myEnvironment = new AndroidEnvironment(context, true, true); } @NotNull public BuildFinishedStatus call() throws Exception { try { final BuildProgressLogger logger = myBuild.getBuildLogger(); logger.targetStarted("Android"); final File buildTempDirectory = myBuild.getBuildTempDirectory(); final String manifestFile = getParameter(PARAM_MANIFEST_FILE, true); final String[] sourcesDirs = getParameterAndSplitToPaths(PARAM_SOURCES_DIRS); final String[] resourcesDirs = getParameterAndSplitToPaths(PARAM_RESOURCES_DIRS); final String assetsDir = getParameter(PARAM_ASSETS_DIR, true); final String[] nativeLibsDirs = getParameterAndSplitToPaths(PARAM_LIBS_DIRS); final String outputFile = getParameter(PARAM_OUTPUT_FILE, true); final boolean isReleaseBuild = Boolean.parseBoolean(getParameter(PARAM_RELEASE_BUILD, false)); final boolean useAdvancedParams = Boolean.parseBoolean(getParameter(PARAM_USE_ADVANCED_PARAMS, false)); final AaptTool aapt = myEnvironment.getAaptTool(); logger.targetStarted("Packaging resources..."); final File resourcesApk = aapt.packageResources(buildTempDirectory, manifestFile, resourcesDirs, assetsDir, getAdditionalOptions(useAdvancedParams, ADV_PARAM_AAPT_PACKAGING_ADDITIONAL_OPTIONS)); logger.targetFinished("Packaging resources..."); logger.targetStarted("Generating R.java..."); final File rFileRootDir = aapt.generateRFile(buildTempDirectory, manifestFile, resourcesDirs, assetsDir, getAdditionalOptions(useAdvancedParams, ADV_PARAM_AAPT_R_GENERATING_ADDITIONAL_OPTIONS)); logger.targetFinished("Generating R.java..."); logger.targetStarted("Compiling sources..."); final File outputRootDir = myEnvironment.getJavacTool().compileSources(buildTempDirectory, rFileRootDir, sourcesDirs, nativeLibsDirs, getAdditionalOptions(useAdvancedParams, ADV_PARAM_JAVAC_ADDITIONAL_OPTIONS)); logger.targetFinished("Compiling sources..."); logger.targetStarted("Packaging classes to .dex file..."); final File dexFile = myEnvironment.getDxTool().packClasses(buildTempDirectory, outputRootDir, nativeLibsDirs, getAdditionalOptions(useAdvancedParams, ADV_PARAM_DX_ADDITIONAL_OPTIONS)); logger.targetFinished("Packaging classes to .dex file..."); logger.targetStarted("Adding classes.dex to result package..."); final boolean isDebug = AndroidRunnersUtil.isDebug(myContext); if (isDebug) { logger.message("Source package: " + resourcesApk.getAbsolutePath()); logger.message("Dex file: " + dexFile.getAbsolutePath()); } final File unsignedApk = ApkUtil.addFileToApk(buildTempDirectory, dexFile, resourcesApk, "classes.dex"); if (isDebug) { logger.message("Result package: " + unsignedApk.getAbsolutePath()); } logger.targetFinished("Adding classes.dex to result package..."); File unalignedApk = unsignedApk; if (AndroidRunnersUtil.mustSignPackage(myContext)) { logger.targetStarted("Signing the package..."); final File keyStore; final String storePass, keyAlias, keyPass; if (isReleaseBuild) { keyStore = new File(getParameter(PARAM_KEY_STORE, true)); storePass = getParameter(PARAM_STORE_PASS, false); keyAlias = getParameter(PARAM_KEY_ALIAS, false); keyPass = getParameter(PARAM_KEY_PASS, false); } else { keyStore = myEnvironment.getKeytoolTool().createDebugKeystore(buildTempDirectory); storePass = DEBUG_STORE_PASS; keyAlias = DEBUG_KEY_ALIAS; keyPass = DEBUG_KEY_PASS; } unalignedApk = myEnvironment.getJarsignerTool().signPackage(buildTempDirectory, unsignedApk, keyStore, storePass, keyAlias, keyPass, getAdditionalOptions(useAdvancedParams, ADV_PARAM_JARSIGNER_ADDITIONAL_OPTIONS)); logger.targetFinished("Signing the package..."); } File finalApk = unalignedApk; if (AndroidRunnersUtil.mustAlignPackage(myContext)) { logger.targetStarted("Aligning the package..."); finalApk = myEnvironment.getZipalignTool().alignPackage(buildTempDirectory, unalignedApk, getAdditionalOptions(useAdvancedParams, ADV_PARAM_ZIPALIGN_ADDITIONAL_OPTIONS)); logger.targetFinished("Aligning the package..."); } FileUtil.copy(finalApk, new File(outputFile)); logger.message("Android package has been built: " + outputFile); logger.targetFinished("Android"); return BuildFinishedStatus.FINISHED_SUCCESS; } catch (final NonZeroExitCodeException e) { return BuildFinishedStatus.FINISHED_FAILED; } } @NotNull private String getAdditionalOptions(final boolean useAdvancedParams, @NotNull final String parameterName) { if (!useAdvancedParams) return ""; final String options = getParameter(parameterName, false); return options == null ? "" : options; } @NotNull private String[] getParameterAndSplitToPaths(@NotNull final String parameterName) { final String parameter = getParameter(parameterName, false); return preparePaths(parameter == null ? new String[0] : parameter.split(LINE_TERMINATORS_REGEX)); } private String getParameter(@NotNull final String parameterName, final boolean isPath) { final String value = myContext.getRunnerParameters().get(parameterName); if (value == null || value.trim().length() == 0) return null; String result = value.trim(); if (isPath) { result = preparePath(result); } return result; } @NotNull private String[] preparePaths(@NotNull final String[] values) { for (int i = 0; i < values.length; i++) { values[i] = preparePath(values[i].trim()); } return values; } @NotNull private String preparePath(@NotNull final String value) { return new File(value).isAbsolute() ? value : new File(myContext.getBuild().getCheckoutDirectory(), value).getAbsolutePath(); } }