aboutsummaryrefslogtreecommitdiffstats
path: root/anttasks
diff options
context:
space:
mode:
authorXavier Ducrohet <xav@android.com>2010-04-28 16:28:35 -0700
committerXavier Ducrohet <xav@android.com>2010-05-03 18:01:55 -0700
commit114ca22329320d7f43af219b473d4c2461e82e7d (patch)
tree798e22bf687d807b8162d7d21788173cbb015752 /anttasks
parent74ab7fbdb2cbe6f9df3ee627a7751b0a3eafbb2a (diff)
downloadsdk-114ca22329320d7f43af219b473d4c2461e82e7d.zip
sdk-114ca22329320d7f43af219b473d4c2461e82e7d.tar.gz
sdk-114ca22329320d7f43af219b473d4c2461e82e7d.tar.bz2
First step of multiple apk support.
- New export type project to handle exporting a single app represented by multiple projects (for different minSdkVersion for instance). - Cleaned-up the current Ant tasks and Eclipse builder to not deal with ApkSettings anymore. It's not possible to generate more than one APK in a single pass. The export project will handle this and call out the normal build system to export each variant. Make older (deprecated) attribute actually output a warning. - Ant rules r3 now with support for multi apk export. Lots of clean up, add new properties to be overriden by the multi-apk export task, make older one overrideable too. - Better handling of older rules, older tasks, etc... in the SetupTask used by the normal build. - Add split by ABI to ApkSettings and use it in the new multi apk export. - New custom task for multi-apk export. Replaces SetupTask for export-type projects. Calls out to the project's build.xml after setting/overriding properties. This also override the project's versionCode with a new composite one made of different values. Also uses a different build.xml, so added a new template: build.export.template. Very much a work in progress. Still to do: Add other types of APK split, create/update export projects, export the build log, ... Also, we need to refactor the normal build rules to avoid so much duplication! Change-Id: I57a565c60d097a5eabb40108ae1fa8cb209f2380
Diffstat (limited to 'anttasks')
-rw-r--r--anttasks/src/com/android/ant/AaptExecLoopTask.java88
-rw-r--r--anttasks/src/com/android/ant/ApkBuilderTask.java161
-rw-r--r--anttasks/src/com/android/ant/MultiApkExportTask.java454
-rw-r--r--anttasks/src/com/android/ant/SetupTask.java114
-rw-r--r--anttasks/src/com/android/ant/TaskHelper.java65
5 files changed, 659 insertions, 223 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecLoopTask.java b/anttasks/src/com/android/ant/AaptExecLoopTask.java
index 09354c0..d9ec6bc 100644
--- a/anttasks/src/com/android/ant/AaptExecLoopTask.java
+++ b/anttasks/src/com/android/ant/AaptExecLoopTask.java
@@ -16,10 +16,6 @@
package com.android.ant;
-import com.android.sdklib.internal.project.ApkSettings;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
-
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
@@ -28,8 +24,6 @@ import org.apache.tools.ant.types.Path;
import java.io.File;
import java.util.ArrayList;
-import java.util.Map;
-import java.util.Map.Entry;
/**
* Task able to run an Exec task on aapt several times.
@@ -85,7 +79,9 @@ public final class AaptExecLoopTask extends Task {
private String mAssets;
private String mAndroidJar;
private String mApkFolder;
- private String mApkBaseName;
+ @Deprecated private String mApkBaseName;
+ private String mApkName;
+ private String mResourceFilter;
private String mRFolder;
private final ArrayList<NoCompress> mNoCompressList = new ArrayList<NoCompress>();
@@ -144,10 +140,12 @@ public final class AaptExecLoopTask extends Task {
* Sets the value of the "resources" attribute.
* @param resources the value.
*
- * @deprecated Uses nested element(s) <res path="value" />
+ * @deprecated Use nested element(s) <res path="value" />
*/
@Deprecated
public void setResources(Path resources) {
+ System.out.println("WARNNG: Using deprecated 'resources' attribute in AaptExecLoopTask." +
+ "Use nested element(s) <res path=\"value\" /> instead.");
if (mResources == null) {
mResources = new ArrayList<Path>();
}
@@ -178,6 +176,8 @@ public final class AaptExecLoopTask extends Task {
*/
@Deprecated
public void setOutfolder(Path outFolder) {
+ System.out.println("WARNNG: Using deprecated 'outfolder' attribute in AaptExecLoopTask." +
+ "Use 'apkfolder' (path) instead.");
mApkFolder = TaskHelper.checkSinglePath("outfolder", outFolder);
}
@@ -196,6 +196,8 @@ public final class AaptExecLoopTask extends Task {
*/
@Deprecated
public void setBasename(String baseName) {
+ System.out.println("WARNNG: Using deprecated 'basename' attribute in AaptExecLoopTask." +
+ "Use 'resourceFilename' (string) instead.");
mApkBaseName = baseName;
}
@@ -204,10 +206,20 @@ public final class AaptExecLoopTask extends Task {
* @param apkbaseName the value.
*/
public void setApkbasename(String apkbaseName) {
+ System.out.println("WARNNG: Using deprecated 'apkbasename' attribute in AaptExecLoopTask." +
+ "Use 'resourceFilename' (string) instead.");
mApkBaseName = apkbaseName;
}
/**
+ * Sets the value of the apkname attribute
+ * @param apkName the value
+ */
+ public void setResourceFilename(String apkName) {
+ mApkName = apkName;
+ }
+
+ /**
* Sets the value of the "rfolder" attribute.
* @param rFolder the value.
*/
@@ -215,6 +227,12 @@ public final class AaptExecLoopTask extends Task {
mRFolder = TaskHelper.checkSinglePath("rfolder", rFolder);
}
+ public void setresourceFilter(String filter) {
+ if (filter != null && filter.length() > 0) {
+ mResourceFilter = filter;
+ }
+ }
+
/**
* Returns an object representing a nested <var>nocompress</var> element.
*/
@@ -251,8 +269,10 @@ public final class AaptExecLoopTask extends Task {
Project taskProject = getProject();
// first do a full resource package
- createPackage(null /*configName*/, null /*resourceFilter*/, null /*customPackage*/);
+ callAapt(null /*customPackage*/);
+ // if the parameters indicate generation of the R class, check if
+ // more R classes need to be created for libraries.
if (mRFolder != null && new File(mRFolder).isDirectory()) {
String libPkgProp = taskProject.getProperty("android.libraries.package");
if (libPkgProp != null) {
@@ -265,49 +285,31 @@ public final class AaptExecLoopTask extends Task {
// FIXME: instead of recreating R.java from scratch, maybe copy
// the files (R.java and manifest.java)? This would force to replace
// the package line on the fly.
- createPackage(null, null, libPkg);
+ callAapt(libPkg);
}
}
}
}
-
- // now see if we need to create file with filtered resources.
- // Get the project base directory.
- File baseDir = taskProject.getBaseDir();
- ProjectProperties properties = ProjectProperties.load(baseDir.getAbsolutePath(),
- PropertyType.DEFAULT);
-
-
- ApkSettings apkSettings = new ApkSettings(properties);
- if (apkSettings != null) {
- Map<String, String> apkFilters = apkSettings.getResourceFilters();
- if (apkFilters.size() > 0) {
- for (Entry<String, String> entry : apkFilters.entrySet()) {
- createPackage(entry.getKey(), entry.getValue(), null /*custom package*/);
- }
- }
- }
}
/**
- * Creates a resource package.
- * @param configName the name of the filter config. Can be null in which case a full resource
- * package will be generated.
+ * Calls aapt with the given parameters.
* @param resourceFilter the resource configuration filter to pass to aapt (if configName is
* non null)
+ * @param customPackage an optional custom package.
*/
- private void createPackage(String configName, String resourceFilter, String customPackage) {
+ private void callAapt(String customPackage) {
Project taskProject = getProject();
final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory();
if (generateRClass) {
- } else if (configName == null || resourceFilter == null) {
+ } else if (mResourceFilter == null) {
System.out.println("Creating full resource package...");
} else {
System.out.println(String.format(
- "Creating resource package for config '%1$s' (%2$s)...",
- configName, resourceFilter));
+ "Creating resource package with filter: (%1$s)...",
+ mResourceFilter));
}
// create a task for the default apk.
@@ -333,9 +335,9 @@ public final class AaptExecLoopTask extends Task {
}
// filters if needed
- if (configName != null && resourceFilter != null) {
+ if (mResourceFilter != null) {
task.createArg().setValue("-c");
- task.createArg().setValue(resourceFilter);
+ task.createArg().setValue(mResourceFilter);
}
// no compress flag
@@ -419,14 +421,14 @@ public final class AaptExecLoopTask extends Task {
}
// apk file. This is based on the apkFolder, apkBaseName, and the configName (if applicable)
- if (mApkBaseName != null && mApkBaseName != null) {
- String filename;
- if (configName != null && resourceFilter != null) {
- filename = mApkBaseName + "-" + configName + ".ap_";
- } else {
- filename = mApkBaseName + ".ap_";
- }
+ String filename = null;
+ if (mApkName != null) {
+ filename = mApkName;
+ } else if (mApkBaseName != null) {
+ filename = mApkBaseName + ".ap_";
+ }
+ if (filename != null) {
File file = new File(mApkFolder, filename);
task.createArg().setValue("-F");
task.createArg().setValue(file.getAbsolutePath());
diff --git a/anttasks/src/com/android/ant/ApkBuilderTask.java b/anttasks/src/com/android/ant/ApkBuilderTask.java
index 9067ec7..e34eb6e 100644
--- a/anttasks/src/com/android/ant/ApkBuilderTask.java
+++ b/anttasks/src/com/android/ant/ApkBuilderTask.java
@@ -19,30 +19,23 @@ package com.android.ant;
import com.android.apkbuilder.ApkBuilder.ApkCreationException;
import com.android.apkbuilder.internal.ApkBuilderImpl;
import com.android.apkbuilder.internal.ApkBuilderImpl.ApkFile;
-import com.android.sdklib.internal.project.ApkSettings;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;
-import org.apache.tools.ant.types.Path.PathElement;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
-import java.util.Map;
-import java.util.Map.Entry;
public class ApkBuilderTask extends Task {
- // ref id to the <path> object containing all the boot classpaths.
- private final static String REF_APK_PATH = "android.apks.path";
-
private String mOutFolder;
- private String mBaseName;
+ @Deprecated private String mBaseName;
+ private String mApkFilepath;
+ private String mResourceFile;
private boolean mVerbose = false;
private boolean mSigned = true;
private boolean mDebug = false;
@@ -72,12 +65,31 @@ public class ApkBuilderTask extends Task {
/**
* Sets the value of the "basename" attribute.
* @param baseName the value.
+ * @deprecated
*/
public void setBasename(String baseName) {
+ System.out.println("WARNNG: Using deprecated 'basename' attribute in ApkBuilderTask." +
+ "Use 'apkFilename' (path) instead.");
mBaseName = baseName;
}
/**
+ * Sets the full filepath to the apk to generate.
+ * @param filepath
+ */
+ public void setApkFilepath(String filepath) {
+ mApkFilepath = filepath;
+ }
+
+ /**
+ * Sets the
+ * @param resourceFile
+ */
+ public void setResourceFile(String resourceFile) {
+ mResourceFile = resourceFile;
+ }
+
+ /**
* Sets the value of the "verbose" attribute.
* @param verbose the value.
*/
@@ -228,57 +240,38 @@ public class ApkBuilderTask extends Task {
}
}
- // create the Path item that will contain all the generated APKs
- // for reuse by other targets (signing/zipaligning)
- Path path = new Path(antProject);
-
- // The createApk method uses mBaseName for the base name of the packages (resources
- // and apk files).
- // The generated apk file name is
- // debug: {base}[-{config}]-debug-unaligned.apk
- // release: {base}[-{config}]-unsigned.apk
- // Unfortunately for 1.5 projects and before the 'install' ant target expects the name
- // of the default debug package to be {base}-debug.apk
- // In order to support those package, we look for the 'out.debug.unaligned.package'
- // property. If this exist, then we generate {base}[-{config}]-debug-unaligned.apk
- // otherwise we generate {base}[-{config}]-debug.apk
- // FIXME: Make apkbuilder export the package name used instead of
- // having to keep apkbuilder and the rules file in sync
- String debugPackageSuffix = "-debug-unaligned.apk";
- if (antProject.getProperty("out.debug.unaligned.package") == null
- && antProject.getProperty("out-debug-unaligned-package") == null) {
- debugPackageSuffix = "-debug.apk";
+ // get the rules revision
+ String rulesRevStr = antProject.getProperty(TaskHelper.PROP_RULES_REV);
+ int rulesRev = 1;
+ try {
+ rulesRev = Integer.parseInt(rulesRevStr);
+ } catch (NumberFormatException e) {
+ // this shouldn't happen since setup task is the one setting up every time.
}
- // first do a full resource package
- createApk(apkBuilder, null /*configName*/, null /*resourceFilter*/, path,
- debugPackageSuffix);
-
- // now see if we need to create file with filtered resources.
- // Get the project base directory.
- File baseDir = antProject.getBaseDir();
- ProjectProperties properties = ProjectProperties.load(baseDir.getAbsolutePath(),
- PropertyType.DEFAULT);
-
- ApkSettings apkSettings = new ApkSettings(properties);
- if (apkSettings != null) {
- Map<String, String> apkFilters = apkSettings.getResourceFilters();
- if (apkFilters.size() > 0) {
- for (Entry<String, String> entry : apkFilters.entrySet()) {
- createApk(apkBuilder, entry.getKey(), entry.getValue(), path,
- debugPackageSuffix);
- }
+
+ File file;
+ if (mApkFilepath != null) {
+ file = new File(mApkFilepath);
+ } else if (rulesRev == 2) {
+ if (mSigned) {
+ file = new File(mOutFolder, mBaseName + "-debug-unaligned.apk");
+ } else {
+ file = new File(mOutFolder, mBaseName + "-unsigned.apk");
}
+ } else {
+ throw new BuildException("missing attribute 'apkFilepath'");
}
- // finally sets the path in the project with a reference
- antProject.addReference(REF_APK_PATH, path);
+ // create the package.
+ createApk(apkBuilder, file);
} catch (FileNotFoundException e) {
throw new BuildException(e);
} catch (IllegalArgumentException e) {
throw new BuildException(e);
} catch (ApkCreationException e) {
+ e.printStackTrace();
throw new BuildException(e);
}
}
@@ -286,76 +279,28 @@ public class ApkBuilderTask extends Task {
/**
* Creates an application package.
* @param apkBuilder
- * @param configName the name of the filter config. Can be null in which case a full resource
- * package will be generated.
- * @param resourceFilter the resource configuration filter to pass to aapt (if configName is
- * non null)
- * @param path Ant {@link Path} to which add the generated APKs as {@link PathElement}
- * @param debugPackageSuffix suffix for the debug packages.
+ * @param outputfile the file to generate
* @throws FileNotFoundException
* @throws ApkCreationException
*/
- private void createApk(ApkBuilderImpl apkBuilder, String configName, String resourceFilter,
- Path path, String debugPackageSuffix)
+ private void createApk(ApkBuilderImpl apkBuilder, File outputfile)
throws FileNotFoundException, ApkCreationException {
- // All the files to be included in the archive have already been prep'ed up, except
- // the resource package.
- // figure out its name.
- String filename;
- if (configName != null && resourceFilter != null) {
- filename = mBaseName + "-" + configName + ".ap_";
- } else {
- filename = mBaseName + ".ap_";
- }
- // now we add it to the list of zip archive (it's just a zip file).
-
- // it's used as a zip archive input
- FileInputStream resoucePackageZipFile = new FileInputStream(new File(mOutFolder, filename));
+ // add the resource pack file as a zip archive input.
+ FileInputStream resoucePackageZipFile = new FileInputStream(
+ new File(mOutFolder, mResourceFile));
mZipArchives.add(resoucePackageZipFile);
- // prepare the filename to generate. Same thing as the resource file.
- if (configName != null && resourceFilter != null) {
- filename = mBaseName + "-" + configName;
- } else {
- filename = mBaseName;
- }
-
if (mSigned) {
- filename = filename + debugPackageSuffix;
+ System.out.println(String.format(
+ "Creating %s and signing it with a debug key...", outputfile.getName()));
} else {
- filename = filename + "-unsigned.apk";
+ System.out.println(String.format(
+ "Creating %s for release...", outputfile.getName()));
}
- if (configName == null || resourceFilter == null) {
- if (mSigned) {
- System.out.println(String.format(
- "Creating %s and signing it with a debug key...", filename));
- } else {
- System.out.println(String.format(
- "Creating %s for release...", filename));
- }
- } else {
- if (mSigned) {
- System.out.println(String.format(
- "Creating %1$s (with %2$s) and signing it with a debug key...",
- filename, resourceFilter));
- } else {
- System.out.println(String.format(
- "Creating %1$s (with %2$s) for release...",
- filename, resourceFilter));
- }
- }
-
- // out File
- File f = new File(mOutFolder, filename);
-
- // add it to the Path object
- PathElement element = path.createPathElement();
- element.setLocation(f);
-
// and generate the apk
- apkBuilder.createPackage(f.getAbsoluteFile(), mZipArchives,
+ apkBuilder.createPackage(outputfile.getAbsoluteFile(), mZipArchives,
mArchiveFiles, mJavaResources, mResourcesJars, mNativeLibraries);
// we are done. We need to remove the resource package from the list of zip archives
diff --git a/anttasks/src/com/android/ant/MultiApkExportTask.java b/anttasks/src/com/android/ant/MultiApkExportTask.java
new file mode 100644
index 0000000..1babcf7
--- /dev/null
+++ b/anttasks/src/com/android/ant/MultiApkExportTask.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 com.android.ant;
+
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.project.ApkSettings;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
+import com.android.sdklib.io.FileWrapper;
+import com.android.sdklib.io.StreamException;
+import com.android.sdklib.xml.AndroidManifest;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Property;
+import org.apache.tools.ant.taskdefs.SubAnt;
+import org.apache.tools.ant.types.FileSet;
+import org.xml.sax.InputSource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+/**
+ * Multiple APK export task.
+ * This task is meant to replace {@link SetupTask} as the main setup/export task, importing
+ * the rules and generating the export for all projects.
+ */
+public class MultiApkExportTask extends Task {
+
+ private static class ExportData implements Comparable<ExportData> {
+
+ String relativePath;
+ File project;
+ int buildInfo;
+ int minor;
+
+ // the following are used to sort the export data and generate buildInfo
+ int minSdkVersion;
+ String abi;
+ int glVersion;
+ // screen size?
+
+ ExportData() {
+ // do nothing.
+ }
+
+ public ExportData(ExportData data) {
+ relativePath = data.relativePath;
+ project = data.project;
+ buildInfo = data.buildInfo;
+ minor = data.buildInfo;
+ minSdkVersion = data.minSdkVersion;
+ abi = data.abi;
+ glVersion = data.glVersion;
+ }
+
+ public int compareTo(ExportData o) {
+ int minSdkDiff = minSdkVersion - o.minSdkVersion;
+ if (minSdkDiff != 0) {
+ return minSdkDiff;
+ }
+
+ if (abi != null) {
+ if (o.abi != null) {
+ return abi.compareTo(o.abi);
+ } else {
+ return -1;
+ }
+ } else if (o.abi != null) {
+ return 1;
+ }
+
+ if (glVersion != 0) {
+ if (o.glVersion != 0) {
+ return glVersion - o.glVersion;
+ } else {
+ return -1;
+ }
+ } else if (o.glVersion != 0) {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+
+ private static enum Target {
+ RELEASE("release"), CLEAN("clean");
+
+ private final String mName;
+
+ Target(String name) {
+ mName = name;
+ }
+
+ String getTarget() {
+ return mName;
+ }
+
+ static Target getTarget(String value) {
+ for (Target t : values()) {
+ if (t.mName.equals(value)) {
+ return t;
+ }
+
+ }
+
+ return null;
+ }
+ }
+
+ private Target mTarget;
+
+ public void setTarget(String target) {
+ mTarget = Target.getTarget(target);
+ }
+
+ @Override
+ public void execute() throws BuildException {
+ Project antProject = getProject();
+
+ if (mTarget == null) {
+ throw new BuildException("'target' attribute not set.");
+ }
+
+ // get the SDK location
+ File sdk = TaskHelper.getSdkLocation(antProject);
+
+ // display SDK Tools revision
+ int toolsRevison = TaskHelper.getToolsRevision(sdk);
+ if (toolsRevison != -1) {
+ System.out.println("Android SDK Tools Revision " + toolsRevison);
+ }
+
+ String appPackage = getValidatedProperty(antProject, "package");
+ System.out.println("Multi APK export for: " + appPackage);
+ String version = getValidatedProperty(antProject, "version");
+ int versionCode;
+ try {
+ versionCode = Integer.parseInt(version);
+ } catch (NumberFormatException e) {
+ throw new BuildException("version value is not a valid integer.", e);
+ }
+ System.out.println("versionCode: " + version);
+
+ ExportData[] projects = getProjects(antProject, appPackage);
+ HashSet<String> compiledProject = new HashSet<String>();
+
+ XPathFactory xPathFactory = XPathFactory.newInstance();
+
+ File exportProjectOutput = new File(getValidatedProperty(antProject, "out.absolute.dir"));
+
+ for (ExportData projectData : projects) {
+ // this output is prepended by "[android-export] " (17 chars), so we put 61 stars
+ System.out.println("\n*************************************************************");
+ System.out.println("Exporting project: " + projectData.relativePath);
+
+ SubAnt subAnt = new SubAnt();
+ subAnt.setTarget(mTarget.getTarget());
+ subAnt.setProject(antProject);
+
+ File subProjectFolder = new File(antProject.getBaseDir(), projectData.relativePath);
+
+ FileSet fileSet = new FileSet();
+ fileSet.setProject(antProject);
+ fileSet.setDir(subProjectFolder);
+ fileSet.setIncludes("build.xml");
+ subAnt.addFileset(fileSet);
+
+// subAnt.setVerbose(true);
+
+ if (mTarget == Target.RELEASE) {
+ // only do the compilation part if it's the first time we export
+ // this project.
+ // (projects can be export multiple time if some properties are set up to
+ // generate more than one APK (for instance ABI split).
+ if (compiledProject.contains(projectData.relativePath) == false) {
+ compiledProject.add(projectData.relativePath);
+ } else {
+ addProp(subAnt, "do.not.compile", "true");
+ }
+
+ // set the version code, and filtering
+ String compositeVersionCode = getVersionCodeString(versionCode, projectData);
+ addProp(subAnt, "version.code", compositeVersionCode);
+ System.out.println("Composite versionCode: " + compositeVersionCode);
+ if (projectData.abi != null) {
+ addProp(subAnt, "filter.abi", projectData.abi);
+ System.out.println("ABI Filter: " + projectData.abi);
+ }
+
+ // end of the output by this task. Everything that follows will be output
+ // by the subant.
+ System.out.println("Calling to project's Ant file...");
+ System.out.println("----------\n");
+
+ // set the output file names/paths. Keep all the temporary files in the project
+ // folder, and only put the final file (which is different depending on whether
+ // the file can be signed) locally.
+ boolean canSign = false;
+
+ // read the base name from the build.xml file.
+ String name = null;
+ try {
+ File buildFile = new File(subProjectFolder, "build.xml");
+ XPath xPath = xPathFactory.newXPath();
+ name = xPath.evaluate("/project/@name",
+ new InputSource(new FileInputStream(buildFile)));
+ } catch (XPathExpressionException e) {
+ throw new BuildException("Failed to read build.xml", e);
+ } catch (FileNotFoundException e) {
+ throw new BuildException("build.xml is missing.", e);
+ }
+
+ // override the resource pack file.
+ addProp(subAnt, "resource.package.file.name",
+ name + "-" + projectData.buildInfo + ".ap_");
+
+ if (canSign) {
+ // temporary file only get a filename change (still stored in the project
+ // bin folder).
+ addProp(subAnt, "out.unsigned.file.name",
+ name + "-" + projectData.buildInfo + "-unsigned.apk");
+ addProp(subAnt, "out.unaligned.file",
+ name + "-" + projectData.buildInfo + "-unaligned.apk");
+
+ // final file is stored locally.
+ addProp(subAnt, "out.release.file", new File(exportProjectOutput,
+ name + "-" + projectData.buildInfo + "-release.apk").getAbsolutePath());
+ } else {
+ // final file is the unsigned version. It gets stored locally.
+ addProp(subAnt, "out.unsigned.file", new File(exportProjectOutput,
+ name + "-" + projectData.buildInfo + "-unsigned.apk").getAbsolutePath());
+ }
+ }
+
+ subAnt.execute();
+ }
+
+ // TODO: export build log.
+
+ }
+
+ /**
+ * Gets, validates and returns a project property.
+ * The property must exist and be non empty.
+ * @param antProject the project
+ * @param name the name of the property to return.
+ * @return the property value always (cannot be null).
+ * @throws BuildException if the property is missing or not valid.
+ */
+ private String getValidatedProperty(Project antProject, String name) {
+ String value = antProject.getProperty(name);
+ if (value == null || value.length() == 0) {
+ throw new BuildException(String.format("Property '%1$s' is not set or empty.", name));
+ }
+
+ return value;
+ }
+
+ /**
+ * gets the projects to export from the property, checks they exist, validates them,
+ * loads their export info and return it.
+ * If a project does not exist or is not valid, this will throw a {@link BuildException}.
+ * @param antProject the Ant project.
+ * @param appPackage the application package. Projects' manifest must match this.
+ */
+ private ExportData[] getProjects(Project antProject, String appPackage) {
+ String projects = antProject.getProperty("projects");
+ String[] paths = projects.split("\\:");
+
+ ArrayList<ExportData> datalist = new ArrayList<ExportData>();
+
+ for (String path : paths) {
+ File projectFolder = new File(path);
+
+ // resolve the path (to remove potential ..)
+ try {
+ projectFolder = projectFolder.getCanonicalFile();
+
+ // project folder must exist and be a directory
+ if (projectFolder.isDirectory() == false) {
+ throw new BuildException(String.format(
+ "Project folder '%1$s' is not a valid directory.",
+ projectFolder.getAbsolutePath()));
+ }
+
+ // Check AndroidManifest.xml is present
+ FileWrapper androidManifest = new FileWrapper(projectFolder,
+ SdkConstants.FN_ANDROID_MANIFEST_XML);
+
+ if (androidManifest.isFile() == false) {
+ throw new BuildException(String.format(
+ "%1$s is not a valid project (%2$s not found).",
+ projectFolder.getAbsolutePath(),
+ SdkConstants.FN_ANDROID_MANIFEST_XML));
+ }
+
+ ArrayList<ExportData> datalist2 = checkManifest(androidManifest, appPackage);
+
+ // if the method returns without throwing, this is a good project to
+ // export.
+ for (ExportData data : datalist2) {
+ data.relativePath = path;
+ data.project = projectFolder;
+ }
+
+ datalist.addAll(datalist2);
+
+ } catch (IOException e) {
+ throw new BuildException(String.format("Failed to resolve path %1$s", path), e);
+ }
+ }
+
+ // sort the projects and assign buildInfo
+ Collections.sort(datalist);
+ int buildInfo = 0;
+ for (ExportData data : datalist) {
+ data.buildInfo = buildInfo++;
+ }
+
+ return datalist.toArray(new ExportData[datalist.size()]);
+ }
+
+ private ArrayList<ExportData> checkManifest(FileWrapper androidManifest, String appPackage) {
+ try {
+ String manifestPackage = AndroidManifest.getPackage(androidManifest);
+ if (appPackage.equals(manifestPackage) == false) {
+ throw new BuildException(String.format(
+ "%1$s package value is not valid. Found '%2$s', expected '%3$s'.",
+ androidManifest.getPath(), manifestPackage, appPackage));
+ }
+
+ if (AndroidManifest.hasVersionCode(androidManifest)) {
+ throw new BuildException(String.format(
+ "%1$s is not valid: versionCode must not be set for multi-apk export.",
+ androidManifest.getPath()));
+ }
+
+ int minSdkVersion = AndroidManifest.getMinSdkVersion(androidManifest);
+ if (minSdkVersion == -1) {
+ throw new BuildException(
+ "Codename in minSdkVersion is not supported by multi-apk export.");
+ }
+
+ ArrayList<ExportData> dataList = new ArrayList<ExportData>();
+ ExportData data = new ExportData();
+ dataList.add(data);
+ data.minSdkVersion = minSdkVersion;
+
+ // only look for more exports if the target is not clean.
+ if (mTarget != Target.CLEAN) {
+ // load the project properties
+ String projectPath = androidManifest.getParent();
+ ProjectProperties projectProp = ProjectProperties.load(projectPath,
+ PropertyType.DEFAULT);
+ if (projectProp == null) {
+ throw new BuildException(String.format(
+ "%1$s is missing.", PropertyType.DEFAULT.getFilename()));
+ }
+
+ ApkSettings apkSettings = new ApkSettings(projectProp);
+ if (apkSettings.isSplitByAbi()) {
+ // need to find the available ABIs.
+ List<String> abis = findAbis(projectPath);
+ ExportData current = data;
+ for (String abi : abis) {
+ if (current == null) {
+ current = new ExportData(data);
+ dataList.add(current);
+ }
+
+ current.abi = abi;
+ current = null;
+ }
+ }
+ }
+
+ return dataList;
+ } catch (XPathExpressionException e) {
+ throw new BuildException(
+ String.format("Failed to validate %1$s", androidManifest.getPath()), e);
+ } catch (StreamException e) {
+ throw new BuildException(
+ String.format("Failed to validate %1$s", androidManifest.getPath()), e);
+ }
+ }
+
+ private List<String> findAbis(String projectPath) {
+ ArrayList<String> abiList = new ArrayList<String>();
+ File libs = new File(projectPath, SdkConstants.FD_NATIVE_LIBS);
+ if (libs.isDirectory()) {
+ File[] abis = libs.listFiles();
+ for (File abi : abis) {
+ if (abi.isDirectory()) {
+ // only add the abi folder if there are .so files in it.
+ String[] content = abi.list(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().endsWith(".so");
+ }
+ });
+
+ if (content.length > 0) {
+ abiList.add(abi.getName());
+ }
+ }
+ }
+ }
+
+ return abiList;
+ }
+
+ private void addProp(SubAnt task, String name, String value) {
+ Property prop = new Property();
+ prop.setName(name);
+ prop.setValue(value);
+ task.addProperty(prop);
+ }
+
+ private String getVersionCodeString(int versionCode, ExportData projectData) {
+ int trueVersionCode = versionCode * 10000;
+ trueVersionCode += projectData.buildInfo * 100;
+ trueVersionCode += projectData.minor;
+
+ return Integer.toString(trueVersionCode);
+ }
+
+}
diff --git a/anttasks/src/com/android/ant/SetupTask.java b/anttasks/src/com/android/ant/SetupTask.java
index a840783..5263f12 100644
--- a/anttasks/src/com/android/ant/SetupTask.java
+++ b/anttasks/src/com/android/ant/SetupTask.java
@@ -39,10 +39,8 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
-import java.util.Properties;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
@@ -81,13 +79,9 @@ public final class SetupTask extends ImportTask {
// ant property with the path to the android.jar
private final static String PROPERTY_ANDROID_JAR = "android.jar";
- // LEGACY - compatibility with 1.6 and before
- private final static String PROPERTY_ANDROID_JAR_LEGACY = "android-jar";
// ant property with the path to the framework.jar
private final static String PROPERTY_ANDROID_AIDL = "android.aidl";
- // LEGACY - compatibility with 1.6 and before
- private final static String PROPERTY_ANDROID_AIDL_LEGACY = "android-aidl";
// ant property with the path to the aapt tool
private final static String PROPERTY_AAPT = "aapt";
@@ -105,26 +99,11 @@ public final class SetupTask extends ImportTask {
Project antProject = getProject();
// get the SDK location
- String sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK);
-
- // check if it's valid and exists
- if (sdkLocation == null || sdkLocation.length() == 0) {
- // LEGACY support: project created with 1.6 or before may be using a different
- // property to declare the location of the SDK. At this point, we cannot
- // yet check which target is running so we check both always.
- sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK_LEGACY);
- if (sdkLocation == null || sdkLocation.length() == 0) {
- throw new BuildException("SDK Location is not set.");
- }
- }
-
- File sdk = new File(sdkLocation);
- if (sdk.isDirectory() == false) {
- throw new BuildException(String.format("SDK Location '%s' is not valid.", sdkLocation));
- }
+ File sdk = TaskHelper.getSdkLocation(antProject);
+ String sdkLocation = sdk.getPath();
// display SDK Tools revision
- int toolsRevison = getToolsRevision(sdk);
+ int toolsRevison = TaskHelper.getToolsRevision(sdk);
if (toolsRevison != -1) {
System.out.println("Android SDK Tools Revision " + toolsRevison);
}
@@ -179,6 +158,14 @@ public final class SetupTask extends ImportTask {
"Unable to resolve target '%s'", targetHashString));
}
+ // display the project info
+ System.out.println("Project Target: " + androidTarget.getName());
+ if (androidTarget.isPlatform() == false) {
+ System.out.println("Vendor: " + androidTarget.getVendor());
+ System.out.println("Platform Version: " + androidTarget.getVersionName());
+ }
+ System.out.println("API level: " + androidTarget.getVersion().getApiString());
+
// check that this version of the custom Ant task can build this target
int antBuildVersion = androidTarget.getProperty(SdkConstants.PROP_SDK_ANT_BUILD_REVISION,
1);
@@ -192,6 +179,39 @@ public final class SetupTask extends ImportTask {
+ "***********************************************************\n\n\n");
}
+ if (antBuildVersion < 2) {
+ // these older rules are obselete, and not versioned, and therefore it's hard
+ // to maintain compatibility.
+
+ // if the platform itself is obsolete, display a different warning
+ if (androidTarget.getVersion().getApiLevel() < 3 ||
+ androidTarget.getVersion().getApiLevel() == 5 ||
+ androidTarget.getVersion().getApiLevel() == 6) {
+ System.out.println("\n\n\n"
+ + "***********************************************************\n"
+ + "WARNING: This platform is obsolete and its Ant rules may not work properly.\n"
+ + "WARNING: It is recommended to develop against a newer version of Android.\n"
+ + "WARNING: For more information about active versions of Android see:\n"
+ + "WARNING: http://developer.android.com/resources/dashboard/platform-versions.html\n"
+ + "***********************************************************\n\n\n");
+ } else {
+ IAndroidTarget baseTarget =
+ androidTarget.getParent() != null ? androidTarget.getParent() : androidTarget;
+ System.out.println(String.format("\n\n\n"
+ + "***********************************************************\n"
+ + "WARNING: Revision %1$d of %2$s uses obsolete Ant rules which may not work properly.\n"
+ + "WARNING: It is recommended that you download a newer revision if available.\n"
+ + "WARNING: For more information about updating your SDK, see:\n"
+ + "WARNING: http://developer.android.com/sdk/adding-components.html\n"
+ + "***********************************************************\n\n\n",
+ baseTarget.getRevision(), baseTarget.getFullName()));
+ }
+ }
+
+ // set a property that contains the rules revision. This can be used by other custom
+ // tasks later.
+ antProject.setProperty(TaskHelper.PROP_RULES_REV, Integer.toString(antBuildVersion));
+
// check if the project is a library
boolean isLibrary = false;
@@ -203,16 +223,9 @@ public final class SetupTask extends ImportTask {
// look for referenced libraries.
processReferencedLibraries(antProject, androidTarget);
- // display the project info
- System.out.println("Project Target: " + androidTarget.getName());
if (isLibrary) {
- System.out.println("Type: Android Library");
- }
- if (androidTarget.isPlatform() == false) {
- System.out.println("Vendor: " + androidTarget.getVendor());
- System.out.println("Platform Version: " + androidTarget.getVersionName());
+ System.out.println("Project Type: Android Library");
}
- System.out.println("API level: " + androidTarget.getVersion().getApiString());
// do a quick check to make sure the target supports library.
if (isLibrary &&
@@ -263,18 +276,6 @@ public final class SetupTask extends ImportTask {
// finally sets the path in the project with a reference
antProject.addReference(REF_CLASSPATH, bootclasspath);
- // LEGACY support. android_rules.xml in older platforms expects properties with
- // older names. This sets those properties to make sure the rules will work.
- if (androidTarget.getVersion().getApiLevel() <= 4) { // 1.6 and earlier
- antProject.setProperty(PROPERTY_ANDROID_JAR_LEGACY, androidJar);
- antProject.setProperty(PROPERTY_ANDROID_AIDL_LEGACY, androidAidl);
- antProject.setProperty(ProjectProperties.PROPERTY_SDK_LEGACY, sdkLocation);
- String appPackage = antProject.getProperty(ProjectProperties.PROPERTY_APP_PACKAGE);
- if (appPackage != null && appPackage.length() > 0) {
- antProject.setProperty(ProjectProperties.PROPERTY_APP_PACKAGE_LEGACY, appPackage);
- }
- }
-
// Now the import section. This is only executed if the task actually has to import a file.
if (mDoImport) {
// find the folder containing the file to import
@@ -504,29 +505,4 @@ public final class SetupTask extends ImportTask {
antProject.setProperty("android.libraries.package", sb.toString());
}
}
-
- /**
- * Returns the revision of the tools for a given SDK.
- * @param sdkFile the {@link File} for the root folder of the SDK
- * @return the tools revision or -1 if not found.
- */
- private int getToolsRevision(File sdkFile) {
- Properties p = new Properties();
- try{
- // tools folder must exist, or this custom task wouldn't run!
- File toolsFolder= new File(sdkFile, SdkConstants.FD_TOOLS);
- File sourceProp = new File(toolsFolder, SdkConstants.FN_SOURCE_PROP);
- p.load(new FileInputStream(sourceProp));
- String value = p.getProperty("Pkg.Revision"); //$NON-NLS-1$
- if (value != null) {
- return Integer.parseInt(value);
- }
- } catch (FileNotFoundException e) {
- // couldn't find the file? return -1 below.
- } catch (IOException e) {
- // couldn't find the file? return -1 below.
- }
-
- return -1;
- }
}
diff --git a/anttasks/src/com/android/ant/TaskHelper.java b/anttasks/src/com/android/ant/TaskHelper.java
index 361725a..b79d274 100644
--- a/anttasks/src/com/android/ant/TaskHelper.java
+++ b/anttasks/src/com/android/ant/TaskHelper.java
@@ -16,12 +16,72 @@
package com.android.ant;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.project.ProjectProperties;
+
import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Path;
-public class TaskHelper {
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Properties;
+
+final class TaskHelper {
+
+ public final static String PROP_RULES_REV = "android.ant.rules.revision";
+
+ static File getSdkLocation(Project antProject) {
+ // get the SDK location
+ String sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK);
+
+ // check if it's valid and exists
+ if (sdkLocation == null || sdkLocation.length() == 0) {
+ // LEGACY support: project created with 1.6 or before may be using a different
+ // property to declare the location of the SDK. At this point, we cannot
+ // yet check which target is running so we check both always.
+ sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK_LEGACY);
+ if (sdkLocation == null || sdkLocation.length() == 0) {
+ throw new BuildException("SDK Location is not set.");
+ }
+ }
+
+ File sdk = new File(sdkLocation);
+ if (sdk.isDirectory() == false) {
+ throw new BuildException(String.format("SDK Location '%s' is not valid.", sdkLocation));
+ }
- public static String checkSinglePath(String attribute, Path path) {
+ return sdk;
+ }
+
+ /**
+ * Returns the revision of the tools for a given SDK.
+ * @param sdkFile the {@link File} for the root folder of the SDK
+ * @return the tools revision or -1 if not found.
+ */
+ static int getToolsRevision(File sdkFile) {
+ Properties p = new Properties();
+ try{
+ // tools folder must exist, or this custom task wouldn't run!
+ File toolsFolder= new File(sdkFile, SdkConstants.FD_TOOLS);
+ File sourceProp = new File(toolsFolder, SdkConstants.FN_SOURCE_PROP);
+ p.load(new FileInputStream(sourceProp));
+ String value = p.getProperty("Pkg.Revision"); //$NON-NLS-1$
+ if (value != null) {
+ return Integer.parseInt(value);
+ }
+ } catch (FileNotFoundException e) {
+ // couldn't find the file? return -1 below.
+ } catch (IOException e) {
+ // couldn't find the file? return -1 below.
+ }
+
+ return -1;
+ }
+
+ static String checkSinglePath(String attribute, Path path) {
String[] paths = path.list();
if (paths.length != 1) {
throw new BuildException(String.format("Path value for '%1$s' is not valid.", attribute));
@@ -29,5 +89,4 @@ public class TaskHelper {
return paths[0];
}
-
}