/* * 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)); } } }