--- /dev/null
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 com.android.ide.eclipse.adt.internal.actions;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AndroidPrintStream;
+import com.android.ide.eclipse.adt.internal.build.BuildHelper;
+import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.io.IFolderWrapper;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.export.ApkData;
+import com.android.sdklib.internal.export.MultiApkExportHelper;
+import com.android.sdklib.internal.export.ProjectConfig;
+import com.android.sdklib.internal.export.MultiApkExportHelper.ExportException;
+import com.android.sdklib.internal.export.MultiApkExportHelper.Target;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IObjectActionDelegate;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.PlatformUI;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Multiple APK export Action.
+ * The action is triggered on a project selection, and performs a full APK export based on the
+ * content of the export.properties file.
+ */
+public class MultiApkExportAction implements IObjectActionDelegate {
+
+ private ISelection mSelection;
+
+ public void setActivePart(IAction action, IWorkbenchPart targetPart) {
+ // pass
+ }
+
+ public void run(IAction action) {
+ if (mSelection instanceof IStructuredSelection) {
+ for (Iterator<?> it = ((IStructuredSelection)mSelection).iterator(); it.hasNext();) {
+ Object element = it.next();
+ IProject project = null;
+ if (element instanceof IProject) {
+ project = (IProject)element;
+ } else if (element instanceof IAdaptable) {
+ project = (IProject)((IAdaptable)element).getAdapter(IProject.class);
+ }
+ if (project != null) {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ final IProject fProject = project;
+ try {
+ workbench.getProgressService().busyCursorWhile(new IRunnableWithProgress() {
+ /**
+ * Run the export.
+ * @throws InvocationTargetException
+ * @throws InterruptedException
+ */
+ public void run(IProgressMonitor monitor)
+ throws InvocationTargetException, InterruptedException {
+ try {
+ runMultiApkExport(fProject, monitor);
+ } catch (Exception e) {
+ AdtPlugin.logAndPrintError(e, fProject.getName(),
+ "Failed to export project: %1$s", e.getMessage());
+ } finally {
+ monitor.done();
+ }
+ }
+ });
+ } catch (Exception e) {
+ AdtPlugin.logAndPrintError(e, project.getName(),
+ "Failed to export project: %1$s",
+ e.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ mSelection = selection;
+ }
+
+ /**
+ * Runs the multi-apk export.
+ * @param exportProject the main "export" project.
+ * @param monitor the progress monitor.
+ * @throws ExportException
+ * @throws CoreException
+ */
+ private void runMultiApkExport(IProject exportProject, IProgressMonitor monitor)
+ throws ExportException, CoreException {
+
+ ProjectProperties props = ProjectProperties.load(new IFolderWrapper(exportProject),
+ PropertyType.EXPORT);
+
+ // get some props and make sure their values are valid.
+
+ String appPackage = props.getProperty(ProjectProperties.PROPERTY_PACKAGE);
+ if (appPackage == null || appPackage.length() == 0) {
+ throw new IllegalArgumentException("Invalid 'package' property values.");
+ }
+
+ String version = props.getProperty(ProjectProperties.PROPERTY_VERSIONCODE);
+ int versionCode;
+ try {
+ versionCode = Integer.parseInt(version);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("version value is not a valid integer.", e);
+ }
+
+ String projects = props.getProperty(ProjectProperties.PROPERTY_PROJECTS);
+ if (projects == null || projects.length() == 0) {
+ throw new IllegalArgumentException("Missing project list.");
+ }
+
+ // create the multi apk helper to get the list of apk to export.
+ MultiApkExportHelper helper = new MultiApkExportHelper(
+ exportProject.getLocation().toOSString(),
+ appPackage, versionCode, Target.RELEASE, System.out);
+
+ List<ApkData> apks = helper.getApkData(projects);
+
+ // list of projects that have been resolved (ie the IProject has been found from the
+ // ProjectConfig) and compiled.
+ HashMap<ProjectConfig, ProjectState> resolvedProjects =
+ new HashMap<ProjectConfig, ProjectState>();
+
+ IWorkspace ws = ResourcesPlugin.getWorkspace();
+ IWorkspaceRoot wsRoot = ws.getRoot();
+
+ // bin folder for the export project
+ IFolder binFolder = exportProject.getFolder(SdkConstants.FD_OUTPUT);
+ if (binFolder.exists() == false) {
+ binFolder.create(true, true, monitor);
+ }
+
+ AndroidPrintStream stdout = new AndroidPrintStream(exportProject, null /*prefix*/,
+ System.out);
+ AndroidPrintStream stderr = new AndroidPrintStream(exportProject, null /*prefix*/,
+ System.err);
+
+ for (ApkData apk : apks) {
+ // find the IProject object for this apk.
+ ProjectConfig projectConfig = apk.getProjectConfig();
+ ProjectState projectState = resolvedProjects.get(projectConfig);
+ if (projectState == null) {
+ // first time? resolve the project and compile it.
+ IPath path = exportProject.getFullPath().append(projectConfig.getRelativePath());
+
+ IResource res = wsRoot.findMember(path);
+ if (res.getType() != IResource.PROJECT) {
+ throw new IllegalArgumentException(String.format(
+ "%1$s does not resolve to a project.",
+ projectConfig.getRelativePath()));
+ }
+
+ IProject project = (IProject)res;
+
+ projectState = Sdk.getProjectState(project);
+ if (projectState == null) {
+ throw new IllegalArgumentException(String.format(
+ "State for project %1$s could not be loaded.",
+ project.getName()));
+ }
+
+ if (projectState.isLibrary()) {
+ throw new IllegalArgumentException(String.format(
+ "Project %1$s is a library and cannot be part of a multi-apk export.",
+ project.getName()));
+ }
+
+ // build the project, mainly for the java compilation. The rest is handled below.
+ project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
+
+ // store the resolved project in the map.
+ resolvedProjects.put(projectConfig, projectState);
+ }
+
+ Map<String, String> variantMap = apk.getSoftVariantMap();
+
+ if (variantMap.size() > 0) {
+ // if there are soft variants, only export those.
+ for (Entry<String, String> entry : variantMap.entrySet()) {
+ buildVariant(wsRoot, projectState, appPackage, versionCode, apk, entry,
+ binFolder, stdout, stderr);
+ }
+ } else {
+ buildVariant(wsRoot, projectState, appPackage, versionCode, apk,
+ null /*soft variant*/, binFolder, stdout, stderr);
+ }
+ }
+
+ helper.writeLogs();
+ }
+
+ /**
+ * Builds a particular variant of an APK
+ * @param wsRoot the workspace root
+ * @param projectState the project to export
+ * @param appPackage the application package
+ * @param versionCode the major version code.
+ * @param apk the {@link ApkData} describing how the export should happen.
+ * @param softVariant an optional soft variant info. The entry contains (name, resource filter).
+ * @param binFolder the binFolder where the file must be created.
+ * @throws CoreException
+ */
+ private void buildVariant(IWorkspaceRoot wsRoot, ProjectState projectState, String appPackage,
+ int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder,
+ AndroidPrintStream stdout, AndroidPrintStream stderr)
+ throws CoreException {
+ try {
+ // get the libraries for this project
+ List<IProject> libProjects = projectState.getFullLibraryProjects();
+
+ IProject project = projectState.getProject();
+ IJavaProject javaProject = JavaCore.create(project);
+
+ int compositeVersionCode = apk.getCompositeVersionCode(versionCode);
+
+ // figure out the file names
+ String pkgName = project.getName() + "-" + apk.getBuildInfo();
+ String finalNameRoot = appPackage + "-" + compositeVersionCode;
+ if (softVariant != null) {
+ String tmp = "-" + softVariant.getKey();
+ pkgName += tmp;
+ finalNameRoot += tmp;
+ }
+
+ pkgName += ".ap_";
+ String outputName = finalNameRoot + "-unsigned.apk";
+
+ BuildHelper helper = new BuildHelper(project, stdout, stderr,
+ false /*debugMode*/, false/*verbose*/);
+
+ // get the manifest file
+ IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
+ // get the project bin folder
+ IFolder projectBinFolder = wsRoot.getFolder(javaProject.getOutputLocation());
+ String projectBinFolderPath = projectBinFolder.getLocation().toOSString();
+
+ // package the resources
+ helper.packageResources(manifestFile, libProjects,
+ softVariant != null ? softVariant.getValue() : null,
+ compositeVersionCode, projectBinFolderPath, pkgName);
+
+ apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName);
+
+ // do the final export.
+ IFile dexFile = projectBinFolder.getFile(SdkConstants.FN_APK_CLASSES_DEX);
+ String outputFile = binFolder.getFile(outputName).getLocation().toOSString();
+
+ // get the list of referenced projects.
+ List<IProject> javaRefs = ProjectHelper.getReferencedProjects(project);
+ List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(javaRefs);
+
+ helper.finalPackage(
+ new File(projectBinFolderPath, pkgName).getAbsolutePath(),
+ dexFile.getLocation().toOSString(),
+ outputFile,
+ javaProject,
+ libProjects,
+ referencedJavaProjects,
+ apk.getAbi(),
+ null, //key
+ null, //certificate
+ null); //ResourceMarker
+ } catch (CoreException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ e.getMessage(), e));
+ }
+
+ }
+}