2 * Copyright (C) 2010 The Android Open Source Project
4 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.eclipse.org/org/documents/epl-v10.php
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.ide.eclipse.adt.internal.actions;
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper;
21 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
22 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
23 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
24 import com.android.ide.eclipse.adt.io.IFolderWrapper;
25 import com.android.sdklib.SdkConstants;
26 import com.android.sdklib.internal.export.ApkData;
27 import com.android.sdklib.internal.export.MultiApkExportHelper;
28 import com.android.sdklib.internal.export.ProjectConfig;
29 import com.android.sdklib.internal.export.MultiApkExportHelper.ExportException;
30 import com.android.sdklib.internal.export.MultiApkExportHelper.Target;
31 import com.android.sdklib.internal.project.ProjectProperties;
32 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
34 import org.eclipse.core.resources.IFile;
35 import org.eclipse.core.resources.IFolder;
36 import org.eclipse.core.resources.IProject;
37 import org.eclipse.core.resources.IResource;
38 import org.eclipse.core.resources.IWorkspace;
39 import org.eclipse.core.resources.IWorkspaceRoot;
40 import org.eclipse.core.resources.IncrementalProjectBuilder;
41 import org.eclipse.core.resources.ResourcesPlugin;
42 import org.eclipse.core.runtime.CoreException;
43 import org.eclipse.core.runtime.IAdaptable;
44 import org.eclipse.core.runtime.IPath;
45 import org.eclipse.core.runtime.IProgressMonitor;
46 import org.eclipse.jdt.core.IJavaProject;
47 import org.eclipse.jdt.core.JavaCore;
48 import org.eclipse.jface.action.IAction;
49 import org.eclipse.jface.operation.IRunnableWithProgress;
50 import org.eclipse.jface.viewers.ISelection;
51 import org.eclipse.jface.viewers.IStructuredSelection;
52 import org.eclipse.ui.IObjectActionDelegate;
53 import org.eclipse.ui.IWorkbench;
54 import org.eclipse.ui.IWorkbenchPart;
55 import org.eclipse.ui.PlatformUI;
58 import java.lang.reflect.InvocationTargetException;
59 import java.util.HashMap;
60 import java.util.Iterator;
61 import java.util.List;
63 import java.util.Map.Entry;
66 * Multiple APK export Action.
67 * The action is triggered on a project selection, and performs a full APK export based on the
68 * content of the export.properties file.
70 public class MultiApkExportAction implements IObjectActionDelegate {
72 private ISelection mSelection;
74 public void setActivePart(IAction action, IWorkbenchPart targetPart) {
78 public void run(IAction action) {
79 if (mSelection instanceof IStructuredSelection) {
80 for (Iterator<?> it = ((IStructuredSelection)mSelection).iterator(); it.hasNext();) {
81 Object element = it.next();
82 IProject project = null;
83 if (element instanceof IProject) {
84 project = (IProject)element;
85 } else if (element instanceof IAdaptable) {
86 project = (IProject)((IAdaptable)element).getAdapter(IProject.class);
88 if (project != null) {
89 IWorkbench workbench = PlatformUI.getWorkbench();
90 final IProject fProject = project;
92 workbench.getProgressService().busyCursorWhile(new IRunnableWithProgress() {
95 * @throws InvocationTargetException
96 * @throws InterruptedException
98 public void run(IProgressMonitor monitor)
99 throws InvocationTargetException, InterruptedException {
101 runMultiApkExport(fProject, monitor);
102 } catch (Exception e) {
103 AdtPlugin.logAndPrintError(e, fProject.getName(),
104 "Failed to export project: %1$s", e.getMessage());
110 } catch (Exception e) {
111 AdtPlugin.logAndPrintError(e, project.getName(),
112 "Failed to export project: %1$s",
120 public void selectionChanged(IAction action, ISelection selection) {
121 mSelection = selection;
125 * Runs the multi-apk export.
126 * @param exportProject the main "export" project.
127 * @param monitor the progress monitor.
128 * @throws ExportException
129 * @throws CoreException
131 private void runMultiApkExport(IProject exportProject, IProgressMonitor monitor)
132 throws ExportException, CoreException {
134 ProjectProperties props = ProjectProperties.load(new IFolderWrapper(exportProject),
135 PropertyType.EXPORT);
137 // get some props and make sure their values are valid.
139 String appPackage = props.getProperty(ProjectProperties.PROPERTY_PACKAGE);
140 if (appPackage == null || appPackage.length() == 0) {
141 throw new IllegalArgumentException("Invalid 'package' property values.");
144 String version = props.getProperty(ProjectProperties.PROPERTY_VERSIONCODE);
147 versionCode = Integer.parseInt(version);
148 } catch (NumberFormatException e) {
149 throw new IllegalArgumentException("version value is not a valid integer.", e);
152 String projects = props.getProperty(ProjectProperties.PROPERTY_PROJECTS);
153 if (projects == null || projects.length() == 0) {
154 throw new IllegalArgumentException("Missing project list.");
157 // create the multi apk helper to get the list of apk to export.
158 MultiApkExportHelper helper = new MultiApkExportHelper(
159 exportProject.getLocation().toOSString(),
160 appPackage, versionCode, Target.RELEASE, System.out);
162 List<ApkData> apks = helper.getApkData(projects);
164 // list of projects that have been resolved (ie the IProject has been found from the
165 // ProjectConfig) and compiled.
166 HashMap<ProjectConfig, ProjectState> resolvedProjects =
167 new HashMap<ProjectConfig, ProjectState>();
169 IWorkspace ws = ResourcesPlugin.getWorkspace();
170 IWorkspaceRoot wsRoot = ws.getRoot();
172 // bin folder for the export project
173 IFolder binFolder = exportProject.getFolder(SdkConstants.FD_OUTPUT);
174 if (binFolder.exists() == false) {
175 binFolder.create(true, true, monitor);
178 for (ApkData apk : apks) {
179 // find the IProject object for this apk.
180 ProjectConfig projectConfig = apk.getProjectConfig();
181 ProjectState projectState = resolvedProjects.get(projectConfig);
182 if (projectState == null) {
183 // first time? resolve the project and compile it.
184 IPath path = exportProject.getFullPath().append(projectConfig.getRelativePath());
186 IResource res = wsRoot.findMember(path);
187 if (res.getType() != IResource.PROJECT) {
188 throw new IllegalArgumentException(String.format(
189 "%1$s does not resolve to a project.",
190 projectConfig.getRelativePath()));
193 IProject project = (IProject)res;
195 projectState = Sdk.getProjectState(project);
196 if (projectState == null) {
197 throw new IllegalArgumentException(String.format(
198 "State for project %1$s could not be loaded.",
202 if (projectState.isLibrary()) {
203 throw new IllegalArgumentException(String.format(
204 "Project %1$s is a library and cannot be part of a multi-apk export.",
208 // build the project, mainly for the java compilation. The rest is handled below.
209 project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
211 // store the resolved project in the map.
212 resolvedProjects.put(projectConfig, projectState);
215 Map<String, String> variantMap = apk.getSoftVariantMap();
217 if (variantMap.size() > 0) {
218 // if there are soft variants, only export those.
219 for (Entry<String, String> entry : variantMap.entrySet()) {
220 buildVariant(wsRoot, projectState, appPackage, versionCode, apk, entry,
224 buildVariant(wsRoot, projectState, appPackage, versionCode, apk,
225 null /*soft variant*/, binFolder);
233 * Builds a particular variant of an APK
234 * @param wsRoot the workspace root
235 * @param projectState the project to export
236 * @param appPackage the application package
237 * @param versionCode the major version code.
238 * @param apk the {@link ApkData} describing how the export should happen.
239 * @param softVariant an optional soft variant info. The entry contains (name, resource filter).
240 * @param binFolder the binFolder where the file must be created.
241 * @throws CoreException
243 private void buildVariant(IWorkspaceRoot wsRoot, ProjectState projectState, String appPackage,
244 int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder)
245 throws CoreException {
246 // get the libraries for this project
247 IProject[] libProjects = projectState.getFullLibraryProjects();
249 IProject project = projectState.getProject();
250 IJavaProject javaProject = JavaCore.create(project);
252 int compositeVersionCode = apk.getCompositeVersionCode(versionCode);
254 // figure out the file names
255 String pkgName = project.getName() + "-" + apk.getBuildInfo();
256 String finalNameRoot = appPackage + "-" + compositeVersionCode;
257 if (softVariant != null) {
258 String tmp = "-" + softVariant.getKey();
260 finalNameRoot += tmp;
264 String outputName = finalNameRoot + "-unsigned.apk";
266 PostCompilerHelper helper = new PostCompilerHelper(project, System.out, System.err);
268 // get the manifest file
269 IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
270 // get the project bin folder
271 IFolder projectBinFolder = wsRoot.getFolder(javaProject.getOutputLocation());
272 String projectBinFolderPath = projectBinFolder.getLocation().toOSString();
274 // package the resources
275 if (helper.packageResources(manifestFile, libProjects,
276 softVariant != null ? softVariant.getValue() : null, compositeVersionCode,
277 projectBinFolderPath, pkgName) == false) {
281 apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName);
283 // do the final export.
284 IFile dexFile = projectBinFolder.getFile(SdkConstants.FN_APK_CLASSES_DEX);
285 String outputFile = binFolder.getFile(outputName).getLocation().toOSString();
287 // get the list of referenced projects.
288 IProject[] javaRefs = ProjectHelper.getReferencedProjects(project);
289 IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(javaRefs);
292 new File(projectBinFolderPath, pkgName).getAbsolutePath(),
293 dexFile.getLocation().toOSString(),
295 false /*debugSign */,
298 referencedJavaProjects,
300 false /*debuggable*/);