diff options
4 files changed, 261 insertions, 136 deletions
diff --git a/anttasks/src/com/android/ant/MultiApkExportTask.java b/anttasks/src/com/android/ant/MultiApkExportTask.java index 8c9ee3e..fd33b92 100644 --- a/anttasks/src/com/android/ant/MultiApkExportTask.java +++ b/anttasks/src/com/android/ant/MultiApkExportTask.java @@ -36,6 +36,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; @@ -49,6 +52,7 @@ import javax.xml.xpath.XPathFactory; public class MultiApkExportTask extends Task { private Target mTarget; + private XPathFactory mXPathFactory; public void setTarget(String target) { mTarget = Target.getTarget(target); @@ -105,9 +109,10 @@ public class MultiApkExportTask extends Task { // some temp var used by the project loop HashSet<String> compiledProject = new HashSet<String>(); - XPathFactory xPathFactory = XPathFactory.newInstance(); + mXPathFactory = XPathFactory.newInstance(); - File exportProjectOutput = new File(getValidatedProperty(antProject, "out.absolute.dir")); + File exportProjectOutput = new File(getValidatedProperty(antProject, + "out.absolute.dir")); // if there's no error, and we can sign, prompt for the passwords. String keyStorePassword = null; @@ -135,105 +140,21 @@ public class MultiApkExportTask extends Task { } for (ApkData apk : apks) { - // this output is prepended by "[android-export] " (17 chars), so we put 61 stars - System.out.println("\n*************************************************************"); - System.out.println("Exporting project: " + apk.getRelativePath()); - - SubAnt subAnt = new SubAnt(); - subAnt.setTarget(mTarget.getTarget()); - subAnt.setProject(antProject); - - File subProjectFolder = new File(antProject.getBaseDir(), apk.getRelativePath()); - - 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(apk.getRelativePath()) == false) { - compiledProject.add(apk.getRelativePath()); - } else { - addProp(subAnt, "do.not.compile", "true"); - } - - // set the version code, and filtering - int compositeVersionCode = apk.getCompositeVersionCode(versionCode); - addProp(subAnt, "version.code", Integer.toString(compositeVersionCode)); - System.out.println("Composite versionCode: " + compositeVersionCode); - String abi = apk.getAbi(); - if (abi != null) { - addProp(subAnt, "filter.abi", abi); - System.out.println("ABI Filter: " + 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. - - // 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 + "-" + apk.getBuildInfo() + ".ap_"); - - if (canSign) { - // set the properties for the password. - addProp(subAnt, "key.store", keyStore); - addProp(subAnt, "key.alias", keyAlias); - addProp(subAnt, "key.store.password", keyStorePassword); - addProp(subAnt, "key.alias.password", keyAliasPassword); - - // temporary file only get a filename change (still stored in the project - // bin folder). - addProp(subAnt, "out.unsigned.file.name", - name + "-" + apk.getBuildInfo() + "-unsigned.apk"); - addProp(subAnt, "out.unaligned.file", - name + "-" + apk.getBuildInfo() + "-unaligned.apk"); - - // final file is stored locally. - apk.setOutputName(name + "-" + compositeVersionCode + "-release.apk"); - addProp(subAnt, "out.release.file", new File(exportProjectOutput, - apk.getOutputName()).getAbsolutePath()); - - } else { - // put some empty prop. This is to override possible ones defined in the - // project. The reason is that if there's more than one project, we don't - // want some to signed and some not to be (and we don't want each project - // to prompt for password.) - addProp(subAnt, "key.store", ""); - addProp(subAnt, "key.alias", ""); - // final file is the unsigned version. It gets stored locally. - apk.setOutputName(name + "-" + compositeVersionCode + "-unsigned.apk"); - addProp(subAnt, "out.unsigned.file", new File(exportProjectOutput, - apk.getOutputName()).getAbsolutePath()); - } + + Map<String, String> variantMap = apk.getSoftVariantMap(); + + // first, do the full export. + makeSubAnt(antProject, appPackage, versionCode, apk, null, + exportProjectOutput, canSign, keyStore, keyAlias, + keyStorePassword, keyAliasPassword, compiledProject); + + // then do the soft variants. + for (Entry<String, String> entry : variantMap.entrySet()) { + makeSubAnt(antProject, appPackage, versionCode, apk, entry, + exportProjectOutput, canSign, keyStore, keyAlias, + keyStorePassword, keyAliasPassword, compiledProject); } - subAnt.execute(); } if (mTarget == Target.RELEASE) { @@ -248,6 +169,143 @@ public class MultiApkExportTask extends Task { } /** + * Creates and executes a sub ant task. + * @param antProject the current Ant project + * @param appPackage the application package string. + * @param versionCode the current version of the application + * @param apk the {@link ApkData} being exported. + * @param softVariant the soft variant being exported, or null, if this is a full export. + * @param exportProjectOutput the folder in which the files must be exported. + * @param canSign whether the application package can be signed. This is dependent on the + * availability of some required values. + * @param keyStore the path to the keystore for signing + * @param keyAlias the alias of the key to be used for signing + * @param keyStorePassword the password of the keystore for signing + * @param keyAliasPassword the password of the key alias for signing + * @param compiledProject a list of projects that have already been compiled. + */ + private void makeSubAnt(Project antProject, String appPackage, int versionCode, + ApkData apk, Entry<String, String> softVariant, File exportProjectOutput, + boolean canSign, String keyStore, String keyAlias, + String keyStorePassword, String keyAliasPassword, Set<String> compiledProject) { + + // this output is prepended by "[android-export] " (17 chars), so we put 61 stars + System.out.println("\n*************************************************************"); + System.out.println("Exporting project: " + apk.getRelativePath()); + + SubAnt subAnt = new SubAnt(); + subAnt.setTarget(mTarget.getTarget()); + subAnt.setProject(antProject); + + File subProjectFolder = new File(antProject.getBaseDir(), apk.getRelativePath()); + + 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(apk.getRelativePath()) == false) { + compiledProject.add(apk.getRelativePath()); + } else { + addProp(subAnt, "do.not.compile", "true"); + } + + // set the version code, and filtering + int compositeVersionCode = apk.getCompositeVersionCode(versionCode); + addProp(subAnt, "version.code", Integer.toString(compositeVersionCode)); + System.out.println("Composite versionCode: " + compositeVersionCode); + String abi = apk.getAbi(); + if (abi != null) { + addProp(subAnt, "filter.abi", abi); + System.out.println("ABI Filter: " + abi); + } + + // 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. + + // read the base name from the build.xml file. + String name = null; + try { + File buildFile = new File(subProjectFolder, "build.xml"); + XPath xPath = mXPathFactory.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 as well as the final name + String pkgName = name + "-" + apk.getBuildInfo(); + String finalNameRoot = appPackage + "-" + compositeVersionCode; + if (softVariant != null) { + String tmp = "-" + softVariant.getKey(); + pkgName += tmp; + finalNameRoot += tmp; + + // set the resource filter. + addProp(subAnt, "aapt.resource.filter", softVariant.getValue()); + System.out.println("res Filter: " + softVariant.getValue()); + } + + // set the resource pack file name. + addProp(subAnt, "resource.package.file.name", pkgName + ".ap_"); + + + if (canSign) { + // set the properties for the password. + addProp(subAnt, "key.store", keyStore); + addProp(subAnt, "key.alias", keyAlias); + addProp(subAnt, "key.store.password", keyStorePassword); + addProp(subAnt, "key.alias.password", keyAliasPassword); + + // temporary file only get a filename change (still stored in the project + // bin folder). + addProp(subAnt, "out.unsigned.file.name", + name + "-" + apk.getBuildInfo() + "-unsigned.apk"); + addProp(subAnt, "out.unaligned.file", + name + "-" + apk.getBuildInfo() + "-unaligned.apk"); + + // final file is stored locally with a name based on the package + String outputName = finalNameRoot + "-release.apk"; + apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName); + addProp(subAnt, "out.release.file", + new File(exportProjectOutput, outputName).getAbsolutePath()); + + } else { + // put some empty prop. This is to override possible ones defined in the + // project. The reason is that if there's more than one project, we don't + // want some to signed and some not to be (and we don't want each project + // to prompt for password.) + addProp(subAnt, "key.store", ""); + addProp(subAnt, "key.alias", ""); + // final file is the unsigned version. It gets stored locally. + String outputName = finalNameRoot + "-unsigned.apk"; + apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName); + addProp(subAnt, "out.unsigned.file", + new File(exportProjectOutput, outputName).getAbsolutePath()); + } + } + + // 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"); + + subAnt.execute(); + } + + /** * Gets, validates and returns a project property. * The property must exist and be non empty. * @param antProject the project diff --git a/changes.txt b/changes.txt index 9bf3a57..282a98b 100644 --- a/changes.txt +++ b/changes.txt @@ -4,8 +4,11 @@ Revision 7: - Support for Ant rules provided by the Tools components (override the one in the platform component) - Headless SDK update. See 'android -h update sdk' for more info. +- Support for extension targets in Ant build to perform tasks between the + normal tasks: -pre-build, -pre-compile, -post-compile. -Revision 6: + +Revision 6 (05/2010) - Support for library project to share code/resources among projects - Updated Ant rules and custom tasks - New "android create lib-project", "android update lib-project" actions. diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java index f2cd97f..c32a360 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java @@ -39,8 +39,9 @@ public class ApkData implements Comparable<ApkData> { private static final String PROP_PROJECT = "project"; private static final String PROP_MINOR = "minor"; private static final String PROP_BUILDINFO = "buildinfo"; + private static final String PROP_DENSITY = "splitDensity"; - private String mOutputName; + private final HashMap<String, String> mOutputNames = new HashMap<String, String>(); private String mRelativePath; private File mProject; private int mBuildInfo; @@ -52,6 +53,9 @@ public class ApkData implements Comparable<ApkData> { private int mGlVersion = ManifestData.GL_ES_VERSION_NOT_SET; private SupportsScreens mSupportsScreens; + // additional apk generation that doesn't impact the build info. + private boolean mSplitDensity; + ApkData() { // do nothing. } @@ -73,12 +77,12 @@ public class ApkData implements Comparable<ApkData> { mSupportsScreens = data.mSupportsScreens; } - public String getOutputName() { - return mOutputName; + public String getOutputName(String key) { + return mOutputNames.get(key); } - public void setOutputName(String outputName) { - mOutputName = outputName; + public void setOutputName(String key, String outputName) { + mOutputNames.put(key, outputName); } public String getRelativePath() { @@ -146,28 +150,59 @@ public class ApkData implements Comparable<ApkData> { return trueVersionCode; } + public void setSplitDensity(boolean splitDensity) { + mSplitDensity = splitDensity; + } + + public boolean isSplitDensity() { + return mSplitDensity; + } + + /** + * Returns a map of pair values (apk name suffix, aapt res filter) to be used to generate + * multiple soft apk variants. + */ + public Map<String, String> getSoftVariantMap() { + HashMap<String, String> map = new HashMap<String, String>(); + if (mSplitDensity) { + map.put("hdpi", "hdpi,nodpi"); + map.put("mdpi", "mdpi,nodpi"); + map.put("ldpi", "ldpi,nodpi"); + } + + return map; + } + @Override public String toString() { - return getLogLine(); + return getLogLine(null); } - public String getLogLine() { + public String getLogLine(String key) { StringBuilder sb = new StringBuilder(); - sb.append(mOutputName).append(':'); - write(sb, PROP_BUILDINFO, mBuildInfo); - write(sb, PROP_MINOR, mMinor); - write(sb, PROP_PROJECT, mRelativePath); - write(sb, PROP_API, mMinSdkVersion); + sb.append(getOutputName(key)).append(':'); + if (key == null) { + write(sb, PROP_BUILDINFO, mBuildInfo); + write(sb, PROP_MINOR, mMinor); + write(sb, PROP_PROJECT, mRelativePath); + write(sb, PROP_API, mMinSdkVersion); + + if (mGlVersion != ManifestData.GL_ES_VERSION_NOT_SET) { + write(sb, PROP_GL, "0x" + Integer.toHexString(mGlVersion)); + } - if (mGlVersion != ManifestData.GL_ES_VERSION_NOT_SET) { - write(sb, PROP_GL, "0x" + Integer.toHexString(mGlVersion)); - } + if (mAbi != null) { + write(sb, PROP_ABI, mAbi); + } - if (mAbi != null) { - write(sb, PROP_ABI, mAbi); - } + if (mSplitDensity) { + write(sb, PROP_DENSITY, Boolean.toString(true)); + } - write(sb, PROP_SCREENS, mSupportsScreens.getEncodedValues()); + write(sb, PROP_SCREENS, mSupportsScreens.getEncodedValues()); + } else { + write(sb, "resources", getSoftVariantMap().get(key)); + } return sb.toString(); } @@ -210,7 +245,8 @@ public class ApkData implements Comparable<ApkData> { public boolean hasSameApkProperties(ApkData apk) { if (mMinSdkVersion != apk.mMinSdkVersion || mSupportsScreens.equals(apk.mSupportsScreens) == false || - mGlVersion != apk.mGlVersion) { + mGlVersion != apk.mGlVersion || + mSplitDensity != apk.mSplitDensity) { return false; } @@ -233,7 +269,7 @@ public class ApkData implements Comparable<ApkData> { */ public void initFromLogLine(String line) { int colon = line.indexOf(':'); - mOutputName = line.substring(0, colon); + mOutputNames.put(null, line.substring(0, colon)); String[] properties = line.substring(colon+1).split(";"); HashMap<String, String> map = new HashMap<String, String>(); for (String prop : properties) { @@ -244,19 +280,25 @@ public class ApkData implements Comparable<ApkData> { } private void setValues(Map<String, String> values) { - mBuildInfo = Integer.parseInt(values.get(PROP_BUILDINFO)); - mMinor = Integer.parseInt(values.get(PROP_MINOR)); - mRelativePath = values.get(PROP_PROJECT); - mMinSdkVersion = Integer.parseInt(values.get(PROP_API)); + String tmp; + try { + mBuildInfo = Integer.parseInt(values.get(PROP_BUILDINFO)); + mMinor = Integer.parseInt(values.get(PROP_MINOR)); + mRelativePath = values.get(PROP_PROJECT); + mMinSdkVersion = Integer.parseInt(values.get(PROP_API)); + + tmp = values.get(PROP_GL); + if (tmp != null) { + mGlVersion = Integer.decode(tmp); + } + } catch (NumberFormatException e) { + // pass. This is probably due to a manual edit, and it'll most likely + // generate an error when matching the log to the current setup. + } - String tmp = values.get(PROP_GL); + tmp = values.get(PROP_DENSITY); if (tmp != null) { - try { - mGlVersion = Integer.decode(tmp); - } catch (NumberFormatException e) { - // pass. This is probably due to a manual edit, and it'll most likely - // generate an error when matching the log to the current setup. - } + mSplitDensity = Boolean.valueOf(tmp); } tmp = values.get(PROP_ABI); diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java index ae70e6f..9340c47 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java @@ -40,6 +40,7 @@ import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import javax.xml.parsers.ParserConfigurationException; @@ -175,8 +176,20 @@ public class MultiApkExportHelper { "# Properties are written as <name>=<value>\n"); for (ApkData apk : apks) { - writer.append(apk.getLogLine()); + writer.append(apk.getLogLine(null)); writer.append('\n'); + + // display the soft variant as comments to the log file. + Map<String, String> softVariants = apk.getSoftVariantMap(); + if (softVariants.size() > 0) { + writer.append("# Soft Variants -- DO NOT UNCOMMENT:\n"); + } + + for (String softVariant : softVariants.keySet()) { + writer.append("# "); + writer.append(apk.getLogLine(softVariant)); + writer.append('\n'); + } } writer.flush(); @@ -241,7 +254,7 @@ public class MultiApkExportHelper { SdkConstants.FN_ANDROID_MANIFEST_XML)); } - ArrayList<ApkData> datalist2 = checkManifest(androidManifest, manifests); + ArrayList<ApkData> datalist2 = checkProject(androidManifest, manifests); // if the method returns without throwing, this is a good project to // export. @@ -268,7 +281,9 @@ public class MultiApkExportHelper { } /** - * Checks a manifest of the project for inclusion in the list of exported APK. + * Checks a project inclusion in the list of exported APK. + * <p/>This performs a check on the manifest, as well as gathers more information about + * mutli-apk from the project's default.properties file. * If the manifest is correct, a list of apk to export is created and returned. * * @param androidManifest the manifest to check @@ -277,7 +292,7 @@ public class MultiApkExportHelper { * @return A new non-null {@link ArrayList} of {@link ApkData}. * @throws ExportException in case of error. */ - private ArrayList<ApkData> checkManifest(IAbstractFile androidManifest, + private ArrayList<ApkData> checkProject(IAbstractFile androidManifest, ArrayList<Manifest> manifests) throws ExportException { try { ManifestData manifestData = AndroidManifestParser.parse(androidManifest); @@ -398,6 +413,13 @@ public class MultiApkExportHelper { current = null; } } + + if (apkSettings.isSplitByDensity()) { + // set this value for all the apk that were create. + for (ApkData apk : dataList) { + apk.setSplitDensity(true); + } + } } return dataList; |