aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXavier Ducrohet <xav@android.com>2010-06-03 19:50:37 -0700
committerXavier Ducrohet <xav@android.com>2010-06-04 10:53:28 -0700
commit9b2c8551d6cd9eadd3934a3511eadd68b1076024 (patch)
tree9914a0c8a09f0d8d0b465b5a58289874c2059f7e
parent2e623920849ce79ad293557d21758add4aea95d8 (diff)
downloadsdk-9b2c8551d6cd9eadd3934a3511eadd68b1076024.zip
sdk-9b2c8551d6cd9eadd3934a3511eadd68b1076024.tar.gz
sdk-9b2c8551d6cd9eadd3934a3511eadd68b1076024.tar.bz2
Rework the multi-apk log file(s).
Move away from a single log file used for: - tell the dev what file was created with that properties - used to increment minor versionCode for specific apks - used to detect config change from what export to another. There are now three files for each case, with the last two using a never changing filename. Only a new build log file is created at each export. Change-Id: Ia9b464e6ffefe24463a537ee48d0a20a7a004af7
-rw-r--r--anttasks/src/com/android/ant/MultiApkExportTask.java356
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ApkData.java309
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/LogHelper.java38
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java705
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ProjectConfig.java305
5 files changed, 1036 insertions, 677 deletions
diff --git a/anttasks/src/com/android/ant/MultiApkExportTask.java b/anttasks/src/com/android/ant/MultiApkExportTask.java
index d22b1e1..7666fbc 100644
--- a/anttasks/src/com/android/ant/MultiApkExportTask.java
+++ b/anttasks/src/com/android/ant/MultiApkExportTask.java
@@ -18,10 +18,9 @@ package com.android.ant;
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.io.FileWrapper;
-import com.android.sdklib.io.IAbstractFile;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
@@ -36,6 +35,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
@@ -86,79 +86,84 @@ public class MultiApkExportTask extends Task {
}
System.out.println("versionCode: " + version);
- // checks whether the projects can be signed.
- boolean canSign = false;
- String keyStore = null, keyAlias = null;
- if (mTarget == Target.RELEASE) {
- String value = antProject.getProperty("key.store");
- keyStore = value != null && value.length() > 0 ? value : null;
- value = antProject.getProperty("key.alias");
- keyAlias = value != null && value.length() > 0 ? value : null;
- canSign = keyStore != null && keyAlias != null;
- }
-
// get the list of projects
- String projects = getValidatedProperty(antProject, "projects");
+ String projectList = getValidatedProperty(antProject, "projects");
- // look to see if there's an export log from a previous export
- IAbstractFile log = getBuildLog(appPackage, versionCode);
+ File rootFolder = antProject.getBaseDir();
+ MultiApkExportHelper helper = new MultiApkExportHelper(rootFolder.getAbsolutePath(),
+ appPackage, versionCode, mTarget);
- MultiApkExportHelper helper = new MultiApkExportHelper(appPackage, versionCode, mTarget);
try {
- ApkData[] apks = helper.getProjects(projects, log);
-
- // some temp var used by the project loop
- HashSet<String> compiledProject = new HashSet<String>();
- mXPathFactory = XPathFactory.newInstance();
-
- 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;
- String keyAliasPassword = null;
- if (canSign) {
- System.out.println("Found signing keystore and key alias. Need passwords.");
-
- Input input = new Input();
- input.setProject(antProject);
- input.setAddproperty("key.store.password");
- input.setMessage(String.format("Please enter keystore password (store: %1$s):",
- keyStore));
- input.execute();
-
- input = new Input();
- input.setProject(antProject);
- input.setAddproperty("key.alias.password");
- input.setMessage(String.format("Please enter password for alias '%1$s':",
- keyAlias));
- input.execute();
-
- // and now read the property so that they can be set into the sub ant task.
- keyStorePassword = getValidatedProperty(antProject, "key.store.password");
- keyAliasPassword = getValidatedProperty(antProject, "key.alias.password");
- }
+ if (mTarget == Target.CLEAN) {
+ // for a clean, we don't need the list of ApkData, we only need the list of
+ // projects
+ List<ProjectConfig> projects = helper.getProjects(projectList);
+ for (ProjectConfig projectConfig : projects) {
+ executeCleanSubAnt(antProject, projectConfig);
+ }
+ } else {
+ // checks whether the projects can be signed.
+ String value = antProject.getProperty("key.store");
+ String keyStore = value != null && value.length() > 0 ? value : null;
+ value = antProject.getProperty("key.alias");
+ String keyAlias = value != null && value.length() > 0 ? value : null;
+ boolean canSign = keyStore != null && keyAlias != null;
+
+ List<ApkData> apks = helper.getApkData(projectList);
+
+ // some temp var used by the project loop
+ HashSet<String> compiledProject = new HashSet<String>();
+ mXPathFactory = XPathFactory.newInstance();
+
+ 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;
+ String keyAliasPassword = null;
+ if (canSign) {
+ System.out.println("Found signing keystore and key alias. Need passwords.");
+
+ Input input = new Input();
+ input.setProject(antProject);
+ input.setAddproperty("key.store.password");
+ input.setMessage(String.format("Please enter keystore password (store: %1$s):",
+ keyStore));
+ input.execute();
+
+ input = new Input();
+ input.setProject(antProject);
+ input.setAddproperty("key.alias.password");
+ input.setMessage(String.format("Please enter password for alias '%1$s':",
+ keyAlias));
+ input.execute();
+
+ // and now read the property so that they can be set into the sub ant task.
+ keyStorePassword = getValidatedProperty(antProject, "key.store.password");
+ keyAliasPassword = getValidatedProperty(antProject, "key.alias.password");
+ }
- for (ApkData apk : apks) {
+ for (ApkData apk : apks) {
- Map<String, String> variantMap = apk.getSoftVariantMap();
+ Map<String, String> variantMap = apk.getSoftVariantMap();
- // first, do the full export.
- makeSubAnt(antProject, appPackage, versionCode, apk, null,
- exportProjectOutput, canSign, keyStore, keyAlias,
- keyStorePassword, keyAliasPassword, compiledProject);
+ if (variantMap.size() > 0) {
+ // if there are soft variants, only export those.
+ for (Entry<String, String> entry : variantMap.entrySet()) {
+ executeReleaseSubAnt(antProject, appPackage, versionCode, apk, entry,
+ exportProjectOutput, canSign, keyStore, keyAlias,
+ keyStorePassword, keyAliasPassword, compiledProject);
+ }
+ } else {
+ // do the full export.
+ executeReleaseSubAnt(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);
+ }
}
- }
-
- if (mTarget == Target.RELEASE) {
- helper.makeBuildLog(log, apks);
+ helper.writeLogs();
}
} catch (ExportException e) {
// we only want to have Ant display the message, not the stack trace, since
@@ -169,7 +174,43 @@ public class MultiApkExportTask extends Task {
}
/**
- * Creates and executes a sub ant task.
+ * Creates and execute a clean sub ant task.
+ * @param antProject the current Ant project
+ * @param projectConfig the project to clean.
+ */
+ private void executeCleanSubAnt(Project antProject, ProjectConfig projectConfig) {
+
+ String relativePath = projectConfig.getRelativePath();
+
+ // this output is prepended by "[android-export] " (17 chars), so we put 61 stars
+ System.out.println("\n*************************************************************");
+ System.out.println("Cleaning project: " + relativePath);
+
+ SubAnt subAnt = new SubAnt();
+ subAnt.setTarget(mTarget.getTarget());
+ subAnt.setProject(antProject);
+
+ File subProjectFolder = projectConfig.getProjectFolder();
+
+ FileSet fileSet = new FileSet();
+ fileSet.setProject(antProject);
+ fileSet.setDir(subProjectFolder);
+ fileSet.setIncludes("build.xml");
+ subAnt.addFileset(fileSet);
+
+ // TODO: send the verbose flag from the main build.xml to the subAnt project.
+ //subAnt.setVerbose(true);
+
+ // 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();
+ }
+
+ /**
+ * Creates and executes a release sub ant task.
* @param antProject the current Ant project
* @param appPackage the application package string.
* @param versionCode the current version of the application
@@ -184,20 +225,22 @@ public class MultiApkExportTask extends Task {
* @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,
+ private void executeReleaseSubAnt(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) {
+ String relativePath = apk.getProjectConfig().getRelativePath();
+
// 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());
+ System.out.println("Exporting project: " + relativePath);
SubAnt subAnt = new SubAnt();
subAnt.setTarget(mTarget.getTarget());
subAnt.setProject(antProject);
- File subProjectFolder = new File(antProject.getBaseDir(), apk.getRelativePath());
+ File subProjectFolder = apk.getProjectConfig().getProjectFolder();
FileSet fileSet = new FileSet();
fileSet.setProject(antProject);
@@ -208,94 +251,92 @@ public class MultiApkExportTask extends Task {
// TODO: send the verbose flag from the main build.xml to the subAnt project.
//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_");
+ // 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(relativePath) == false) {
+ compiledProject.add(relativePath);
+ } 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);
+ }
- 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);
+ // 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.
- // 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");
+ // 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);
+ }
- // 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());
+ // 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());
+ }
- } 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());
- }
+ // 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
@@ -323,7 +364,6 @@ public class MultiApkExportTask extends Task {
return value;
}
-
/**
* Adds a property to a {@link SubAnt} task.
* @param task the task.
@@ -336,14 +376,4 @@ public class MultiApkExportTask extends Task {
prop.setValue(value);
task.addProperty(prop);
}
-
- /**
- * Returns the {@link File} for the build log.
- * @param appPackage
- * @param versionCode
- * @return A new non-null {@link IAbstractFile} mapping to the build log.
- */
- private IAbstractFile getBuildLog(String appPackage, int versionCode) {
- return new FileWrapper(appPackage + "." + versionCode + ".log");
- }
}
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 471070a..b743477 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
@@ -16,34 +16,25 @@
package com.android.sdklib.internal.export;
-import com.android.sdklib.internal.project.ApkSettings;
import com.android.sdklib.xml.ManifestData;
-import com.android.sdklib.xml.ManifestData.SupportsScreens;
-import java.io.File;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import java.util.Map.Entry;
/**
- * Class representing one apk that needs to be generated. This contains
- * which project it must be created from, and which filters should be used.
+ * Class representing one apk (or more if there are soft variants) that needs to be generated.
+ * This contains a link to the project used for the export, and which extra filters should be used.
*
* This class is meant to be sortable in a way that allows generation of the buildInfo
* value that goes in the composite versionCode.
*/
-public class ApkData implements Comparable<ApkData> {
+public final class ApkData implements Comparable<ApkData> {
- private static final String PROP_SCREENS = "screens";
- private static final String PROP_ABI = "abi";
- private static final String PROP_GL = "gl";
- private static final String PROP_API = "api";
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 static final String PROP_LOCALEFILTERS = "localeFilters";
+ private static final String PROP_BUILDINFO = "buildInfo";
+ private static final String PROP_MINOR = "minorCode";
+ private static final String PROP_ABI = "abi";
+ private static final String PROP_RESOURCES = "resources";
/**
* List of ABI order.
@@ -57,75 +48,45 @@ public class ApkData implements Comparable<ApkData> {
new String[] { "armeabi", "armeabi-v7a" }
};
- /**
- * List of densities and their associated aapt filter.
- */
- private static final String[][] DENSITY_LIST = new String[][] {
- new String[] { "hdpi", "hdpi,nodpi" },
- new String[] { "mdpi", "mdpi,nodpi" },
- new String[] { "ldpi", "ldpi,nodpi" },
- };
-
+ private final ProjectConfig mProjectConfig;
private final HashMap<String, String> mOutputNames = new HashMap<String, String>();
- private String mRelativePath;
- private File mProject;
private int mBuildInfo;
- private int mMinor;
+ private int mMinorCode;
// the following are used to sort the export data and generate buildInfo
- private int mMinSdkVersion;
- private String mAbi;
- 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;
- private final HashMap<String, String> mLocaleFilters = new HashMap<String, String>();
- private Map<String, String> mSoftVariantMap;
-
- ApkData() {
- // do nothing.
- }
+ private final String mAbi;
+ private final Map<String, String> mSoftVariantMap = new HashMap<String, String>();
- ApkData(int minSdkVersion, SupportsScreens supportsScreens, int glEsVersion) {
- mMinSdkVersion = minSdkVersion;
- mSupportsScreens = supportsScreens;
- mGlVersion = glEsVersion;
+ ApkData(ProjectConfig projectConfig, String abi, Map<String, String> softVariants) {
+ mProjectConfig = projectConfig;
+ mAbi = abi;
+ if (softVariants != null) {
+ mSoftVariantMap.putAll(softVariants);
+ }
}
- ApkData(ApkData data) {
- mRelativePath = data.mRelativePath;
- mProject = data.mProject;
- mBuildInfo = data.mBuildInfo;
- mMinor = data.mBuildInfo;
- mMinSdkVersion = data.mMinSdkVersion;
- mAbi = data.mAbi;
- mGlVersion = data.mGlVersion;
- mSupportsScreens = data.mSupportsScreens;
+ ApkData(ProjectConfig projectConfig, String abi) {
+ this(projectConfig, abi, null /*softVariants*/);
}
- public String getOutputName(String key) {
- return mOutputNames.get(key);
+ ApkData(ProjectConfig projectConfig, Map<String, String> softVariants) {
+ this(projectConfig, null /*abi*/, softVariants);
}
- public void setOutputName(String key, String outputName) {
- mOutputNames.put(key, outputName);
+ ApkData(ProjectConfig projectConfig) {
+ this(projectConfig, null /*abi*/, null /*softVariants*/);
}
- public String getRelativePath() {
- return mRelativePath;
+ public ProjectConfig getProjectConfig() {
+ return mProjectConfig;
}
- void setRelativePath(String relativePath) {
- mRelativePath = relativePath;
- }
-
- public File getProject() {
- return mProject;
+ public String getOutputName(String key) {
+ return mOutputNames.get(key);
}
- void setProject(File project) {
- mProject = project;
+ public void setOutputName(String key, String outputName) {
+ mOutputNames.put(key, outputName);
}
public int getBuildInfo() {
@@ -136,32 +97,20 @@ public class ApkData implements Comparable<ApkData> {
mBuildInfo = buildInfo;
}
- public int getMinor() {
- return mMinor;
- }
-
- void setMinor(int minor) {
- mMinor = minor;
+ public int getMinorCode() {
+ return mMinorCode;
}
- public int getMinSdkVersion() {
- return mMinSdkVersion;
+ void setMinorCode(int minor) {
+ mMinorCode = minor;
}
public String getAbi() {
return mAbi;
}
- void setAbi(String abi) {
- mAbi = abi;
- }
-
- public int getGlVersion() {
- return mGlVersion;
- }
-
- public SupportsScreens getSupportsScreens() {
- return mSupportsScreens;
+ public Map<String, String> getSoftVariantMap() {
+ return mSoftVariantMap;
}
/**
@@ -172,55 +121,11 @@ public class ApkData implements Comparable<ApkData> {
public int getCompositeVersionCode(int versionCode) {
int trueVersionCode = versionCode * MultiApkExportHelper.OFFSET_VERSION_CODE;
trueVersionCode += getBuildInfo() * MultiApkExportHelper.OFFSET_BUILD_INFO;
- trueVersionCode += getMinor();
+ trueVersionCode += getMinorCode();
return trueVersionCode;
}
- synchronized void setSplitDensity(boolean splitDensity) {
- mSplitDensity = splitDensity;
- mSoftVariantMap = null;
-
- }
-
- synchronized void setLocaleFilters(Map<String, String> localeFilters) {
- mLocaleFilters.clear();
- mLocaleFilters.putAll(localeFilters);
- mSoftVariantMap = null;
- }
-
- /**
- * Returns a map of pair values (apk name suffix, aapt res filter) to be used to generate
- * multiple soft apk variants.
- */
- public synchronized Map<String, String> getSoftVariantMap() {
- if (mSoftVariantMap == null) {
- HashMap<String, String> map = new HashMap<String, String>();
-
- if (mSplitDensity && mLocaleFilters.size() > 0) {
- for (String[] density : DENSITY_LIST) {
- for (Entry<String,String> entry : mLocaleFilters.entrySet()) {
- map.put(density[0] + "-" + entry.getKey(),
- density[1] + "," + entry.getValue());
- }
- }
-
- } else if (mSplitDensity) {
- for (String[] density : DENSITY_LIST) {
- map.put(density[0], density[1]);
- }
-
- } else if (mLocaleFilters.size() > 0) {
- map.putAll(mLocaleFilters);
-
- }
-
- mSoftVariantMap = Collections.unmodifiableMap(map);
- }
-
- return mSoftVariantMap;
- }
-
@Override
public String toString() {
return getLogLine(null);
@@ -229,69 +134,62 @@ public class ApkData implements Comparable<ApkData> {
public String getLogLine(String key) {
StringBuilder sb = new StringBuilder();
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 (mAbi != null) {
- write(sb, PROP_ABI, mAbi);
- }
+ LogHelper.write(sb, PROP_BUILDINFO, mBuildInfo);
+ LogHelper.write(sb, PROP_MINOR, mMinorCode);
+ LogHelper.write(sb, PROP_PROJECT, mProjectConfig.getRelativePath());
+ sb.append(mProjectConfig.getConfigString(true /*onlyManifestData*/));
- if (mSplitDensity) {
- write(sb, PROP_DENSITY, Boolean.toString(true));
- }
-
- if (mLocaleFilters.size() > 0) {
- write(sb, PROP_LOCALEFILTERS, ApkSettings.writeLocaleFilters(mLocaleFilters));
- }
+ if (mAbi != null) {
+ LogHelper.write(sb, PROP_ABI, mAbi);
+ }
- write(sb, PROP_SCREENS, mSupportsScreens.getEncodedValues());
- } else {
- write(sb, "resources", getSoftVariantMap().get(key));
+ String filter = mSoftVariantMap.get(key);
+ if (filter != null) {
+ LogHelper.write(sb, PROP_RESOURCES, filter);
}
return sb.toString();
}
public int compareTo(ApkData o) {
- int minSdkDiff = mMinSdkVersion - o.mMinSdkVersion;
+ // compare only the hard properties, and in a specific order:
+
+ // 1. minSdkVersion
+ int minSdkDiff = mProjectConfig.getMinSdkVersion() - o.mProjectConfig.getMinSdkVersion();
if (minSdkDiff != 0) {
return minSdkDiff;
}
+ // 2. <supports-screens>
// only compare if they have don't have the same size support. This is because
// this compare method throws an exception if the values cannot be compared.
- if (mSupportsScreens.hasSameScreenSupportAs(o.mSupportsScreens) == false) {
- return mSupportsScreens.compareScreenSizesWith(o.mSupportsScreens);
+ if (mProjectConfig.getSupportsScreens().hasSameScreenSupportAs(
+ o.mProjectConfig.getSupportsScreens()) == false) {
+ return mProjectConfig.getSupportsScreens().compareScreenSizesWith(
+ o.mProjectConfig.getSupportsScreens());
}
+ // 3. glEsVersion
int comp;
- if (mGlVersion != ManifestData.GL_ES_VERSION_NOT_SET) {
- if (o.mGlVersion != ManifestData.GL_ES_VERSION_NOT_SET) {
- comp = mGlVersion - o.mGlVersion;
+ if (mProjectConfig.getGlEsVersion() != ManifestData.GL_ES_VERSION_NOT_SET) {
+ if (o.mProjectConfig.getGlEsVersion() != ManifestData.GL_ES_VERSION_NOT_SET) {
+ comp = mProjectConfig.getGlEsVersion() - o.mProjectConfig.getGlEsVersion();
if (comp != 0) return comp;
} else {
return -1;
}
- } else if (o.mGlVersion != ManifestData.GL_ES_VERSION_NOT_SET) {
+ } else if (o.mProjectConfig.getGlEsVersion() != ManifestData.GL_ES_VERSION_NOT_SET) {
return 1;
}
+ // 4. ABI
// here the returned value is only important if both abi are non null.
if (mAbi != null && o.mAbi != null) {
comp = compareAbi(mAbi, o.mAbi);
if (comp != 0) return comp;
}
- // Do not compare mSplitDensity or mLocaleFilter because they do not generate build info,
- // and also, we should already have a difference at this point.
-
return 0;
}
@@ -317,89 +215,4 @@ public class ApkData implements Comparable<ApkData> {
return 0;
}
-
- public boolean hasSameApkProperties(ApkData apk) {
- if (mMinSdkVersion != apk.mMinSdkVersion ||
- mSupportsScreens.equals(apk.mSupportsScreens) == false ||
- mGlVersion != apk.mGlVersion ||
- mSplitDensity != apk.mSplitDensity ||
- mLocaleFilters.equals(apk.mLocaleFilters) == false) {
- return false;
- }
-
- if (mAbi != null) {
- if (mAbi.equals(apk.mAbi) == false) {
- return false;
- }
- } else if (apk.mAbi != null) {
- return false;
- }
-
- return true;
- }
-
- /**
- * reads the apk description from a log line.
- * @param line The fields to read, comma-separated.
- *
- * @see #getLogLine()
- */
- public void initFromLogLine(String line) {
- int colon = line.indexOf(':');
- 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) {
- colon = prop.indexOf('=');
- map.put(prop.substring(0, colon), prop.substring(colon+1));
- }
- setValues(map);
- }
-
- private synchronized void setValues(Map<String, String> values) {
- 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.
- }
-
- tmp = values.get(PROP_DENSITY);
- if (tmp != null) {
- mSplitDensity = Boolean.valueOf(tmp);
- }
-
- tmp = values.get(PROP_ABI);
- if (tmp != null) {
- mAbi = tmp;
- }
-
- tmp = values.get(PROP_SCREENS);
- if (tmp != null) {
- mSupportsScreens = new SupportsScreens(tmp);
- }
-
- tmp = values.get(PROP_LOCALEFILTERS);
- if (tmp != null) {
- mLocaleFilters.putAll(ApkSettings.readLocaleFilters(tmp));
- }
- mSoftVariantMap = null;
- }
-
- private void write(StringBuilder sb, String name, Object value) {
- sb.append(name + "=").append(value).append(';');
- }
-
- private void write(StringBuilder sb, String name, int value) {
- sb.append(name + "=").append(value).append(';');
- }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/LogHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/LogHelper.java
new file mode 100644
index 0000000..b324b4d
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/LogHelper.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sdklib.internal.export;
+
+class LogHelper {
+
+ /**
+ * Separator for putting multiple properties in a single {@link String}.
+ */
+ final static char PROP_SEPARATOR = ';';
+ /**
+ * Equal sign between the name and value of a property
+ */
+ final static char PROPERTY_EQUAL = '=';
+
+ static void write(StringBuilder sb, String name, Object value) {
+ sb.append(name).append(PROPERTY_EQUAL).append(value).append(PROP_SEPARATOR);
+ }
+
+ static void write(StringBuilder sb, String name, int value) {
+ sb.append(name).append(PROPERTY_EQUAL).append(value).append(PROP_SEPARATOR);
+ }
+
+}
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 dad57e7..697ce8b 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
@@ -17,15 +17,9 @@
package com.android.sdklib.internal.export;
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.IAbstractFile;
-import com.android.sdklib.io.IAbstractFolder;
-import com.android.sdklib.io.IAbstractResource;
import com.android.sdklib.io.StreamException;
-import com.android.sdklib.io.IAbstractFolder.FilenameFilter;
import com.android.sdklib.xml.AndroidManifestParser;
import com.android.sdklib.xml.ManifestData;
import com.android.sdklib.xml.ManifestData.SupportsScreens;
@@ -34,11 +28,16 @@ import org.xml.sax.SAXException;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.Collections;
+import java.util.Formatter;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -46,18 +45,30 @@ import javax.xml.parsers.ParserConfigurationException;
/**
* Helper to export multiple APKs from 1 or or more projects.
+ * <strong>This class is not meant to be accessed from multiple threads</strong>
*/
public class MultiApkExportHelper {
+ private final static String PROP_VERSIONCODE = "versionCode";
+ private final static String PROP_PACKAGE = "package";
+
+ private final String mExportProjectRoot;
private final String mAppPackage;
private final int mVersionCode;
private final Target mTarget;
+ private ArrayList<ProjectConfig> mProjectList;
+ private ArrayList<ApkData> mApkDataList;
+
final static int MAX_MINOR = 100;
final static int MAX_BUILDINFO = 100;
final static int OFFSET_BUILD_INFO = MAX_MINOR;
final static int OFFSET_VERSION_CODE = OFFSET_BUILD_INFO * MAX_BUILDINFO;
+ private final static String FILE_CONFIG = "projects.config";
+ private final static String FILE_MINOR_CODE = "minor.codes";
+ private final static String FOLDER_LOG = "logs";
+
public static final class ExportException extends Exception {
private static final long serialVersionUID = 1L;
@@ -65,6 +76,14 @@ public class MultiApkExportHelper {
super(message);
}
+ public ExportException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public ExportException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
public ExportException(String message, Throwable cause) {
super(message, cause);
}
@@ -95,101 +114,168 @@ public class MultiApkExportHelper {
}
}
+ public MultiApkExportHelper(String exportProjectRoot, String appPackage,
+ int versionCode, Target target) {
+ mExportProjectRoot = exportProjectRoot;
+ mAppPackage = appPackage;
+ mVersionCode = versionCode;
+ mTarget = target;
+ }
+
+ public List<ApkData> getApkData(String projectList) throws ExportException {
+ if (mTarget != Target.RELEASE) {
+ throw new IllegalArgumentException("getApkData must only be called for Target.RELEASE");
+ }
+
+ // get the list of apk to export and their configuration.
+ List<ProjectConfig> projects = getProjects(projectList);
+
+ // look to see if there's a config file from a previous export
+ File configProp = new File(mExportProjectRoot, FILE_CONFIG);
+ if (configProp.isFile()) {
+ compareProjectsToConfigFile(projects, configProp);
+ }
+
+ // look to see if there's a minor properties file
+ File minorCodeProp = new File(mExportProjectRoot, FILE_MINOR_CODE);
+ Map<Integer, Integer> minorCodeMap = null;
+ if (minorCodeProp.isFile()) {
+ minorCodeMap = getMinorCodeMap(minorCodeProp);
+ }
+
+ // get the apk from the projects.
+ return getApkData(projects, minorCodeMap);
+ }
+
/**
- * Simple class to hold a {@link ManifestData} and the {@link IAbstractFile} representing
- * the parsed manifest file.
+ * Returns the list of projects defined by the <var>projectList</var> string.
+ * The projects are checked to be valid Android project and to represent a valid set
+ * of projects for multi-apk export.
+ * If a project does not exist or is not valid, the method will throw a {@link BuildException}.
+ * The string must be a list of paths, relative to the export project path (given to
+ * {@link #MultiApkExportHelper(String, String, int, Target)}), separated by the colon (':')
+ * character. The path separator is expected to be forward-slash ('/') on all platforms.
+ * @param projects the string containing all the relative paths to the projects. This is
+ * usually read from export.properties.
+ * @throws ExportException
*/
- private static class Manifest {
- final IAbstractFile file;
- final ManifestData data;
+ public List<ProjectConfig> getProjects(String projectList) throws ExportException {
+ String[] paths = projectList.split("\\:");
- Manifest(IAbstractFile file, ManifestData data) {
- this.file = file;
- this.data = data;
+ mProjectList = new ArrayList<ProjectConfig>();
+
+ for (String path : paths) {
+ path = path.replaceAll("\\/", File.separator);
+ processProject(path, mProjectList);
}
+
+ return mProjectList;
}
- public MultiApkExportHelper(String appPackage, int versionCode, Target target) {
- mAppPackage = appPackage;
- mVersionCode = versionCode;
- mTarget = target;
+ /**
+ * Writes post-export logs and other files.
+ * @throws ExportException if writing the files failed.
+ */
+ public void writeLogs() throws ExportException {
+ writeConfigProperties();
+ writeMinorVersionProperties();
+ writeApkLog();
}
- public ApkData[] getProjects(String projects, IAbstractFile buildLog) throws ExportException {
- // get the list of apk to export and their configuration.
- ApkData[] apks = getProjects(projects);
+ private void writeConfigProperties() throws ExportException {
+ OutputStreamWriter writer = null;
+ try {
+ writer = new OutputStreamWriter(
+ new FileOutputStream(new File(mExportProjectRoot, FILE_CONFIG)));
- // look to see if there's an export log from a previous export
- if (mTarget == Target.RELEASE && buildLog != null && buildLog.exists()) {
- // load the log and compare to current export list.
- // Any difference will force a new versionCode.
- ApkData[] previousApks = getProjects(buildLog);
+ writer.append("# PROJECT CONFIG -- DO NOT DELETE.\n");
+ writeValue(writer, PROP_VERSIONCODE, mVersionCode);
- if (previousApks.length != apks.length) {
- throw new ExportException(String.format(
- "Project export is setup differently from previous export at versionCode %d.\n" +
- "Any change in the multi-apk configuration requires an increment of the versionCode in export.properties.",
- mVersionCode));
+ for (ProjectConfig project : mProjectList) {
+ writeValue(writer,project.getRelativePath(),
+ project.getConfigString(false /*onlyManifestData*/));
}
- for (int i = 0 ; i < previousApks.length ; i++) {
- // update the minor value from what is in the log file.
- apks[i].setMinor(previousApks[i].getMinor());
- if (apks[i].hasSameApkProperties(previousApks[i]) == false) {
- throw new ExportException(String.format(
- "Project export is setup differently from previous export at versionCode %d.\n" +
- "Any change in the multi-apk configuration requires an increment of the versionCode.",
- mVersionCode));
+ writer.flush();
+ } catch (Exception e) {
+ throw new ExportException("Failed to write config log", e);
+ } finally {
+ try {
+ if (writer != null) {
+ writer.close();
}
+ } catch (IOException e) {
+ throw new ExportException("Failed to write config log", e);
}
}
-
- return apks;
}
- /**
- * Writes the build log for a given list of {@link ApkData}.
- * @param buildLog the build log file into which to write the log.
- * @param apks the list of apks that were exported.
- * @throws ExportException
- */
- public void makeBuildLog(IAbstractFile buildLog, ApkData[] apks) throws ExportException {
+ private void writeMinorVersionProperties() throws ExportException {
OutputStreamWriter writer = null;
try {
- writer = new OutputStreamWriter(buildLog.getOutputStream());
+ writer = new OutputStreamWriter(
+ new FileOutputStream(new File(mExportProjectRoot, FILE_MINOR_CODE)));
writer.append(
- "# Multi-APK BUILD LOG.\n" +
- "# This file serves two purpose:\n" +
- "# - A log of what was built, showing what went in each APK and their properties.\n" +
- "# You can refer to this if you get a bug report for a specific versionCode.\n" +
- "# - A way to update builds through minor revisions for specific APKs.\n" +
- "# Only edit manually to change the minor properties for build you wish to respin.\n" +
- "# Note that all APKs will be regenerated all the time.\n");
+ "# Minor version codes.\n" +
+ "# To create update to select APKs without updating the main versionCode\n" +
+ "# edit this file and manually increase the minor version for the select\n" +
+ "# build info.\n" +
+ "# Format of the file is <buildinfo>:<minor>\n");
+ writeValue(writer, PROP_VERSIONCODE, mVersionCode);
+
+ for (ApkData apk : mApkDataList) {
+ writeValue(writer, Integer.toString(apk.getBuildInfo()), apk.getMinorCode());
+ }
+
+ writer.flush();
+ } catch (Exception e) {
+ throw new ExportException("Failed to write minor log", e);
+ } finally {
+ try {
+ if (writer != null) {
+ writer.close();
+ }
+ } catch (IOException e) {
+ throw new ExportException("Failed to write minor log", e);
+ }
+ }
+ }
+
+ private void writeApkLog() throws ExportException {
+ OutputStreamWriter writer = null;
+ try {
+ File logFolder = new File(mExportProjectRoot, FOLDER_LOG);
+ if (logFolder.isFile()) {
+ throw new ExportException("Cannot create folder '%1$s', file is in the way!",
+ FOLDER_LOG);
+ } else if (logFolder.exists() == false) {
+ logFolder.mkdir();
+ }
- writeValue(writer, "package", mAppPackage);
- writeValue(writer, "versionCode", mVersionCode);
+ Formatter formatter = new Formatter();
+ formatter.format("%1$s.%2$d-%3$tY%3$tm%3$td-%3$tH%3$tM.log",
+ mAppPackage, mVersionCode,
+ Calendar.getInstance().getTime());
- writer.append(
- "# The format of the following lines is:\n" +
- "# <filename>:<property1>;<property2>;<property3>;...\n" +
- "# Properties are written as <name>=<value>\n");
+ writer = new OutputStreamWriter(
+ new FileOutputStream(new File(logFolder, formatter.toString())));
- for (ApkData apk : apks) {
- writer.append(apk.getLogLine(null));
- writer.append('\n');
+ writer.append("# Multi-APK BUILD LOG.\n");
+ writeValue(writer, PROP_PACKAGE, mAppPackage);
+ writeValue(writer, PROP_VERSIONCODE, mVersionCode);
- // display the soft variants for this apkData as comments to the log file.
- // since they all share the same Build Info and cannot be modified by the dev
- // and we won't read them back from the log.
+ for (ApkData apk : mApkDataList) {
+ // if there are soft variant, do not display the main log line, as it's not actually
+ // exported.
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));
+ for (String softVariant : softVariants.keySet()) {
+ writer.append(apk.getLogLine(softVariant));
+ writer.append('\n');
+ }
+ } else {
+ writer.append(apk.getLogLine(null));
writer.append('\n');
}
}
@@ -210,116 +296,114 @@ public class MultiApkExportHelper {
private void writeValue(OutputStreamWriter writer, String name, String value)
throws IOException {
- writer.append(name).append('=').append(value).append('\n');
+ writer.append(name).append(':').append(value).append('\n');
}
private void writeValue(OutputStreamWriter writer, String name, int value) throws IOException {
writeValue(writer, name, Integer.toString(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 projects the string containing all the relative paths to the projects. This is
- * usually read from export.properties.
- * @throws ExportException
- */
- private ApkData[] getProjects(String projects) throws ExportException {
- String[] paths = projects.split("\\:");
-
- ArrayList<ApkData> datalist = new ArrayList<ApkData>();
- ArrayList<Manifest> manifests = new ArrayList<Manifest>();
-
- 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 ExportException(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 ExportException(String.format(
- "%1$s is not a valid project (%2$s not found).",
- projectFolder.getAbsolutePath(),
- SdkConstants.FN_ANDROID_MANIFEST_XML));
- }
-
- ArrayList<ApkData> datalist2 = checkProject(androidManifest, manifests);
-
- // if the method returns without throwing, this is a good project to
- // export.
- for (ApkData data : datalist2) {
- data.setRelativePath(path);
- data.setProject(projectFolder);
- }
-
- datalist.addAll(datalist2);
+ private List<ApkData> getApkData(List<ProjectConfig> projects,
+ Map<Integer, Integer> minorCodes) {
+ mApkDataList = new ArrayList<ApkData>();
- } catch (IOException e) {
- throw new ExportException(String.format("Failed to resolve path %1$s", path), e);
- }
+ // get all the apkdata from all the projects
+ for (ProjectConfig config : projects) {
+ mApkDataList.addAll(config.getApkDataList());
}
// sort the projects and assign buildInfo
- Collections.sort(datalist);
+ Collections.sort(mApkDataList);
int buildInfo = 0;
- for (ApkData data : datalist) {
- data.setBuildInfo(buildInfo++);
+ for (ApkData data : mApkDataList) {
+ data.setBuildInfo(buildInfo);
+ if (minorCodes != null) {
+ Integer minorCode = minorCodes.get(buildInfo);
+ if (minorCode != null) {
+ data.setMinorCode(minorCode);
+ }
+ }
+
+ buildInfo++;
}
- return datalist.toArray(new ApkData[datalist.size()]);
+ return mApkDataList;
}
/**
- * Checks a project inclusion in the list of exported APK.
+ * Checks a project for 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
- * @param manifests list of manifests that were already parsed. Must be filled with the current
- * manifest being checked.
- * @return A new non-null {@link ArrayList} of {@link ApkData}.
+ * @param projectFolder the folder of the project to check
+ * @param projects the list of project to file with the project if it passes validation.
* @throws ExportException in case of error.
*/
- private ArrayList<ApkData> checkProject(IAbstractFile androidManifest,
- ArrayList<Manifest> manifests) throws ExportException {
+ private void processProject(String relativePath,
+ ArrayList<ProjectConfig> projects) throws ExportException {
+
+ // resolve the relative path
+ File projectFolder;
+ try {
+ File path = new File(mExportProjectRoot, relativePath);
+
+ projectFolder = path.getCanonicalFile();
+
+ // project folder must exist and be a directory
+ if (projectFolder.isDirectory() == false) {
+ throw new ExportException(
+ "Project folder '%1$s' is not a valid directory.",
+ projectFolder.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ throw new ExportException(
+ e, "Failed to resolve path %1$s", relativePath);
+ }
+
try {
+ // Check AndroidManifest.xml is present
+ IAbstractFile androidManifest = new FileWrapper(projectFolder,
+ SdkConstants.FN_ANDROID_MANIFEST_XML);
+
+ if (androidManifest.exists() == false) {
+ throw new ExportException(String.format(
+ "%1$s is not a valid project (%2$s not found).",
+ relativePath, androidManifest.getOsLocation()));
+ }
+
+ // output the relative path resolution.
+ System.out.println(String.format("%1$s => %2$s", relativePath,
+ projectFolder.getAbsolutePath()));
+
+ // parse the manifest of the project.
ManifestData manifestData = AndroidManifestParser.parse(androidManifest);
+ // validate the application package name
String manifestPackage = manifestData.getPackage();
if (mAppPackage.equals(manifestPackage) == false) {
- throw new ExportException(String.format(
+ throw new ExportException(
"%1$s package value is not valid. Found '%2$s', expected '%3$s'.",
- androidManifest.getOsLocation(), manifestPackage, mAppPackage));
+ androidManifest.getOsLocation(), manifestPackage, mAppPackage);
}
+ // validate that the manifest has no versionCode set.
if (manifestData.getVersionCode() != null) {
- throw new ExportException(String.format(
+ throw new ExportException(
"%1$s is not valid: versionCode must not be set for multi-apk export.",
- androidManifest.getOsLocation()));
+ androidManifest.getOsLocation());
}
+ // validate that the minSdkVersion is not a codename
int minSdkVersion = manifestData.getMinSdkVersion();
if (minSdkVersion == ManifestData.MIN_SDK_CODENAME) {
throw new ExportException(
"Codename in minSdkVersion is not supported by multi-apk export.");
}
- // compare to other existing manifest.
- for (Manifest previousManifest : manifests) {
+ // compare to other projects already processed to make sure that they are not
+ // identical.
+ for (ProjectConfig otherProject : projects) {
// Multiple apk export support difference in:
// - min SDK Version
// - Screen version
@@ -327,26 +411,27 @@ public class MultiApkExportHelper {
// - ABI (not managed at the Manifest level).
// if those values are the same between 2 manifest, then it's an error.
+
// first the minSdkVersion.
- if (minSdkVersion == previousManifest.data.getMinSdkVersion()) {
+ if (minSdkVersion == otherProject.getMinSdkVersion()) {
// if it's the same compare the rest.
SupportsScreens currentSS = manifestData.getSupportsScreensValues();
- SupportsScreens previousSS = previousManifest.data.getSupportsScreensValues();
+ SupportsScreens previousSS = otherProject.getSupportsScreens();
boolean sameSupportsScreens = currentSS.hasSameScreenSupportAs(previousSS);
// if it's the same, then it's an error. Can't export 2 projects that have the
// same approved (for multi-apk export) hard-properties.
- if (manifestData.getGlEsVersion() == previousManifest.data.getGlEsVersion() &&
+ if (manifestData.getGlEsVersion() == otherProject.getGlEsVersion() &&
sameSupportsScreens) {
- throw new ExportException(String.format(
+ throw new ExportException(
"Android manifests must differ in at least one of the following values:\n" +
"- minSdkVersion\n" +
"- SupportsScreen (screen sizes only)\n" +
"- GL ES version.\n" +
"%1$s and %2$s are considered identical for multi-apk export.",
- androidManifest.getOsLocation(),
- previousManifest.file.getOsLocation()));
+ relativePath,
+ otherProject.getRelativePath());
}
// At this point, either supports-screens or GL are different.
@@ -361,176 +446,264 @@ public class MultiApkExportHelper {
// (ie APK1 supports small/large and APK2 supports normal).
if (sameSupportsScreens == false) {
if (currentSS.hasStrictlyDifferentScreenSupportAs(previousSS) == false) {
- throw new ExportException(String.format(
+ throw new ExportException(
"APK differentiation by Supports-Screens cannot support different APKs supporting the same screen size.\n" +
"%1$s supports %2$s\n" +
"%3$s supports %4$s\n",
- androidManifest.getOsLocation(), currentSS.toString(),
- previousManifest.file.getOsLocation(), previousSS.toString()));
+ relativePath, currentSS.toString(),
+ otherProject.getRelativePath(), previousSS.toString());
}
if (currentSS.overlapWith(previousSS)) {
- throw new ExportException(String.format(
+ throw new ExportException(
"Unable to compute APK priority due to incompatible difference in Supports-Screens values.\n" +
"%1$s supports %2$s\n" +
"%3$s supports %4$s\n",
- androidManifest.getOsLocation(), currentSS.toString(),
- previousManifest.file.getOsLocation(), previousSS.toString()));
+ relativePath, currentSS.toString(),
+ otherProject.getRelativePath(), previousSS.toString());
}
}
}
}
- // add the current manifest to the list
- manifests.add(new Manifest(androidManifest, manifestData));
-
- ArrayList<ApkData> dataList = new ArrayList<ApkData>();
- ApkData data = new ApkData(minSdkVersion, manifestData.getSupportsScreensValues(),
- manifestData.getGlEsVersion());
- dataList.add(data);
-
- // only look for more exports if the target is not clean.
- if (mTarget != Target.CLEAN) {
- // load the project properties
- IAbstractFolder projectFolder = androidManifest.getParentFolder();
- ProjectProperties projectProp = ProjectProperties.load(projectFolder,
- PropertyType.DEFAULT);
- if (projectProp == null) {
- throw new ExportException(String.format(
- "%1$s is missing.", PropertyType.DEFAULT.getFilename()));
- }
-
- ApkSettings apkSettings = new ApkSettings(projectProp);
-
- // get the density/locale values
- boolean splitByDensity = apkSettings.isSplitByDensity();
- Map<String, String> localeFilters = apkSettings.getLocaleFilters();
-
- if (apkSettings.isSplitByAbi()) {
- // need to find the available ABIs.
- List<String> abis = findAbis(projectFolder);
- ApkData current = data;
- for (String abi : abis) {
- if (current == null) {
- current = new ApkData(data);
- dataList.add(current);
- }
-
- current.setAbi(abi);
- current.setSplitDensity(splitByDensity);
- current.setLocaleFilters(localeFilters);
+ // project passes first validation. Attempt to create a ProjectConfig object.
- current = null;
- }
- }
- }
-
- return dataList;
+ ProjectConfig config = ProjectConfig.create(projectFolder, relativePath, manifestData);
+ projects.add(config);
} catch (SAXException e) {
- throw new ExportException(
- String.format("Failed to validate %1$s", androidManifest.getOsLocation()), e);
+ throw new ExportException(e, "Failed to validate %1$s", relativePath);
} catch (IOException e) {
- throw new ExportException(
- String.format("Failed to validate %1$s", androidManifest.getOsLocation()), e);
+ throw new ExportException(e, "Failed to validate %1$s", relativePath);
} catch (StreamException e) {
- throw new ExportException(
- String.format("Failed to validate %1$s", androidManifest.getOsLocation()), e);
+ throw new ExportException(e, "Failed to validate %1$s", relativePath);
} catch (ParserConfigurationException e) {
- throw new ExportException(
- String.format("Failed to validate %1$s", androidManifest.getOsLocation()), e);
+ throw new ExportException(e, "Failed to validate %1$s", relativePath);
}
}
/**
- * Loads and returns a list of {@link ApkData} from a build log.
- * @param log
- * @return A new non-null, possibly empty, array of {@link ApkData}.
- * @throws ExportException
- * @throws BuildException in case of error.
+ * Checks an existing list of {@link ProjectConfig} versus a config file.
+ * @param projects the list of projects to check
+ * @param configProp the config file (must have been generated from a previous export)
+ * @return true if the projects and config file match
+ * @throws ExportException in case of error
*/
- private ApkData[] getProjects(IAbstractFile buildLog) throws ExportException {
- ArrayList<ApkData> datalist = new ArrayList<ApkData>();
-
+ private void compareProjectsToConfigFile(List<ProjectConfig> projects, File configProp)
+ throws ExportException {
InputStreamReader reader = null;
BufferedReader bufferedReader = null;
try {
- reader = new InputStreamReader(buildLog.getContents());
+ reader = new InputStreamReader(new FileInputStream(configProp));
bufferedReader = new BufferedReader(reader);
String line;
- int lineIndex = 0;
+
+ // List of the ProjectConfig that need to be checked. This is to detect
+ // new Projects added to the setup.
+ // removed projects are detected when an entry in the config file doesn't match
+ // any ProjectConfig in the list.
+ ArrayList<ProjectConfig> projectsToCheck = new ArrayList<ProjectConfig>();
+ projectsToCheck.addAll(projects);
+
+ // store the project that doesn't match.
+ ProjectConfig badMatch = null;
+
+ // recorded whether we checked the version code. this is for when we compare
+ // a project config
+ boolean checkedVersion = false;
+
+ int lineNumber = 0;
while ((line = bufferedReader.readLine()) != null) {
+ lineNumber++;
line = line.trim();
if (line.length() == 0 || line.startsWith("#")) {
continue;
}
- switch (lineIndex) {
- case 0:
- // read package value
- lineIndex++;
- break;
- case 1:
- // read versionCode value
- lineIndex++;
- break;
- default:
- // read apk description
- ApkData data = new ApkData();
- datalist.add(data);
- data.initFromLogLine(line);
- if (data.getMinor() >= MAX_MINOR) {
+ // read the name of the property
+ int colonPos = line.indexOf(':');
+ if (colonPos == -1) {
+ // looks like there's an invalid line!
+ throw new ExportException(
+ "Failed to read existing build log. Line %d is not a property line.",
+ lineNumber);
+ }
+
+ String name = line.substring(0, colonPos);
+ String value = line.substring(colonPos + 1);
+
+ if (PROP_VERSIONCODE.equals(name)) {
+ try {
+ int versionCode = Integer.parseInt(value);
+ if (versionCode < mVersionCode) {
+ // this means this config file is obsolete and we can ignore it.
+ return;
+ } else if (versionCode > mVersionCode) {
+ // we're exporting at a lower versionCode level than the config file?
throw new ExportException(
- "Valid minor version code values are 0-" + (MAX_MINOR-1));
+ "Incompatible versionCode: Exporting at versionCode %1$d but %2$s file indicate previous export with versionCode %3$d.",
+ mVersionCode, FILE_CONFIG, versionCode);
+ } else if (badMatch != null) {
+ // looks like versionCode is a match, but a project
+ // isn't compatible.
+ break;
+ } else {
+ // record that we did check the versionCode
+ checkedVersion = true;
+ }
+ } catch (NumberFormatException e) {
+ throw new ExportException(
+ "Failed to read integer property %1$s at line %2$d.",
+ PROP_VERSIONCODE, lineNumber);
+ }
+ } else {
+ // looks like this is (or should be) a project line.
+ // name of the property is the relative path.
+ // look for a matching project.
+ ProjectConfig found = null;
+ for (int i = 0 ; i < projectsToCheck.size() ; i++) {
+ ProjectConfig p = projectsToCheck.get(i);
+ if (p.getRelativePath().equals(name)) {
+ found = p;
+ projectsToCheck.remove(i);
+ break;
+ }
+ }
+
+ if (found == null) {
+ // deleted project!
+ throw new ExportException(
+ "Project %1$s has been removed from the list of projects to export.\n" +
+ "Any change in the multi-apk configuration requires an increment of the versionCode in export.properties.",
+ name);
+ } else {
+ // make a map of properties
+ HashMap<String, String> map = new HashMap<String, String>();
+ String[] properties = value.split(";");
+ for (String prop : properties) {
+ int equalPos = prop.indexOf('=');
+ map.put(prop.substring(0, equalPos), prop.substring(equalPos + 1));
+ }
+
+ if (found.compareToProperties(map) == false) {
+ // bad project config, record the project
+ badMatch = found;
+
+ // if we've already checked that the versionCode didn't already change
+ // we stop right away.
+ if (checkedVersion) {
+ break;
+ }
}
- break;
+ }
+
}
+
+ }
+
+ if (badMatch != null) {
+ throw new ExportException(
+ "Config for project %1$s has changed from previous export with versionCode %2$d.\n" +
+ "Any change in the multi-apk configuration requires an increment of the versionCode in export.properties.",
+ badMatch.getRelativePath(), mVersionCode);
+ } else if (projectsToCheck.size() > 0) {
+ throw new ExportException(
+ "Project %1$s was not part of the previous export with versionCode %2$d.\n" +
+ "Any change in the multi-apk configuration requires an increment of the versionCode in export.properties.",
+ projectsToCheck.get(0).getRelativePath(), mVersionCode);
}
+
} catch (IOException e) {
- throw new ExportException("Failed to read existing build log", e);
- } catch (StreamException e) {
- throw new ExportException("Failed to read existing build log", e);
+ throw new ExportException(e, "Failed to read existing config log: %s", FILE_CONFIG);
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
- throw new ExportException("Failed to read existing build log", e);
+ throw new ExportException(e, "Failed to read existing config log: %s", FILE_CONFIG);
}
}
-
- return datalist.toArray(new ApkData[datalist.size()]);
}
- /**
- * Finds ABIs in a project folder. This is based on the presence of libs/<abi>/ folder.
- *
- * @param projectPath The OS path of the project.
- * @return A new non-null, possibly empty, list of ABI strings.
- */
- private List<String> findAbis(IAbstractFolder projectFolder) {
- ArrayList<String> abiList = new ArrayList<String>();
- IAbstractFolder libs = projectFolder.getFolder(SdkConstants.FD_NATIVE_LIBS);
- if (libs.exists()) {
- IAbstractResource[] abis = libs.listMembers();
- for (IAbstractResource abi : abis) {
- if (abi instanceof IAbstractFolder && abi.exists()) {
- // only add the abi folder if there are .so files in it.
- String[] content = ((IAbstractFolder)abi).list(new FilenameFilter() {
- public boolean accept(IAbstractFolder dir, String name) {
- return name.toLowerCase().endsWith(".so");
- }
- });
+ private Map<Integer, Integer> getMinorCodeMap(File minorProp) throws ExportException {
+ InputStreamReader reader = null;
+ BufferedReader bufferedReader = null;
+ try {
+ reader = new InputStreamReader(new FileInputStream(minorProp));
+ bufferedReader = new BufferedReader(reader);
+ String line;
+
+ boolean foundVersionCode = false;
+ Map<Integer, Integer> map = new HashMap<Integer, Integer>();
+
+ int lineNumber = 0;
+ while ((line = bufferedReader.readLine()) != null) {
+ lineNumber++;
+ line = line.trim();
+ if (line.length() == 0 || line.startsWith("#")) {
+ continue;
+ }
+
+ // read the name of the property
+ int colonPos = line.indexOf(':');
+ if (colonPos == -1) {
+ // looks like there's an invalid line!
+ throw new ExportException(
+ "Failed to read existing build log. Line %d is not a property line.",
+ lineNumber);
+ }
- if (content.length > 0) {
- abiList.add(abi.getName());
+ String name = line.substring(0, colonPos);
+ String value = line.substring(colonPos + 1);
+
+ if (PROP_VERSIONCODE.equals(name)) {
+ try {
+ int versionCode = Integer.parseInt(value);
+ if (versionCode < mVersionCode) {
+ // this means this minor file is obsolete and we can ignore it.
+ return null;
+ } else if (versionCode > mVersionCode) {
+ // we're exporting at a lower versionCode level than the minor file?
+ throw new ExportException(
+ "Incompatible versionCode: Exporting at versionCode %1$d but %2$s file indicate previous export with versionCode %3$d.",
+ mVersionCode, FILE_MINOR_CODE, versionCode);
+ }
+ foundVersionCode = true;
+ } catch (NumberFormatException e) {
+ throw new ExportException(
+ "Failed to read integer property %1$s at line %2$d.",
+ PROP_VERSIONCODE, lineNumber);
+ }
+ } else {
+ try {
+ map.put(Integer.valueOf(name), Integer.valueOf(value));
+ } catch (NumberFormatException e) {
+ throw new ExportException(
+ "Failed to read buildInfo property '%1$s' at line %2$d.",
+ line, lineNumber);
}
}
}
- }
-
- return abiList;
- }
+ // if there was no versionCode found, we can't garantee that the minor version
+ // found are for this versionCode
+ if (foundVersionCode == false) {
+ throw new ExportException(
+ "%1$s property missing from file %2$s.", PROP_VERSIONCODE, FILE_MINOR_CODE);
+ }
+ return map;
+ } catch (IOException e) {
+ throw new ExportException(e, "Failed to read existing minor log: %s", FILE_MINOR_CODE);
+ } finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (IOException e) {
+ throw new ExportException(e, "Failed to read existing minor log: %s",
+ FILE_MINOR_CODE);
+ }
+ }
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ProjectConfig.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ProjectConfig.java
new file mode 100644
index 0000000..8b7d653
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/ProjectConfig.java
@@ -0,0 +1,305 @@
+/*
+ * 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.sdklib.internal.export;
+
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.export.MultiApkExportHelper.ExportException;
+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.resources.Density;
+import com.android.sdklib.xml.ManifestData;
+import com.android.sdklib.xml.ManifestData.SupportsScreens;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Class representing an Android project and its properties.
+ *
+ * Only the properties that pertain to the multi-apk export are present.
+ */
+public final class ProjectConfig {
+
+ private static final String PROP_API = "api";
+ private static final String PROP_SCREENS = "screens";
+ private static final String PROP_GL = "gl";
+ private static final String PROP_ABI = "splitByAbi";
+ private static final String PROP_DENSITY = "splitByDensity";
+ private static final String PROP_LOCALEFILTERS = "localeFilters";
+
+ /**
+ * List of densities and their associated aapt filter.
+ */
+ private static final String[][] DENSITY_LIST = new String[][] {
+ new String[] { Density.HIGH.getValue(),
+ Density.HIGH.getValue() + "," + Density.NODPI.getValue() },
+ new String[] { Density.MEDIUM.getValue(),
+ Density.MEDIUM.getValue() + "," + Density.NODPI.getValue() },
+ new String[] { Density.MEDIUM.getValue(),
+ Density.MEDIUM.getValue() + "," + Density.NODPI.getValue() },
+ };
+
+ private final File mProjectFolder;
+ private final String mRelativePath;
+
+ private final int mMinSdkVersion;
+ private final int mGlEsVersion;
+ private final SupportsScreens mSupportsScreens;
+ private final boolean mSplitByAbi;
+ private final boolean mSplitByDensity;
+ private final Map<String, String> mLocaleFilters;
+
+ static ProjectConfig create(File projectFolder, String relativePath,
+ ManifestData manifestData) throws ExportException {
+ // load the project properties
+ ProjectProperties projectProp = ProjectProperties.load(projectFolder.getAbsolutePath(),
+ PropertyType.DEFAULT);
+ if (projectProp == null) {
+ throw new ExportException(String.format("%1$s is missing for project %2$s",
+ PropertyType.DEFAULT.getFilename(), relativePath));
+ }
+
+ ApkSettings apkSettings = new ApkSettings(projectProp);
+
+ return new ProjectConfig(projectFolder,
+ relativePath,
+ manifestData.getMinSdkVersion(),
+ manifestData.getGlEsVersion(),
+ manifestData.getSupportsScreensValues(),
+ apkSettings.isSplitByAbi(),
+ apkSettings.isSplitByDensity(),
+ apkSettings.getLocaleFilters());
+ }
+
+
+ private ProjectConfig(File projectFolder, String relativePath,
+ int minSdkVersion, int glEsVersion,
+ SupportsScreens supportsScreens, boolean splitByAbi, boolean splitByDensity,
+ Map<String, String> localeFilters) {
+ mProjectFolder = projectFolder;
+ mRelativePath = relativePath;
+ mMinSdkVersion = minSdkVersion;
+ mGlEsVersion = glEsVersion;
+ mSupportsScreens = supportsScreens;
+ mSplitByAbi = splitByAbi;
+ mSplitByDensity = splitByDensity;
+ mLocaleFilters = localeFilters;
+ }
+
+ public File getProjectFolder() {
+ return mProjectFolder;
+ }
+
+
+ public String getRelativePath() {
+ return mRelativePath;
+ }
+
+ List<ApkData> getApkDataList() {
+ // there are 3 cases:
+ // 1. ABI split generate multiple apks with different build info, so they are different
+ // ApkData for all of them. Special case: split by abi but no native code => 1 ApkData.
+ // 2. split by density or locale filters generate soft variant only, so they all go
+ // in the same ApkData.
+ // 3. Both 1. and 2. means that more than one ApkData are created and they all get soft
+ // variants.
+
+ ArrayList<ApkData> list = new ArrayList<ApkData>();
+
+ Map<String, String> softVariants = computeSoftVariantMap();
+
+ if (mSplitByAbi) {
+ List<String> abis = findAbis();
+ if (abis.size() > 0) {
+ for (String abi : abis) {
+ list.add(new ApkData(this, abi, softVariants));
+ }
+ } else {
+ // if there are no ABIs, then just generate a single ApkData with no specific ABI.
+ list.add(new ApkData(this, softVariants));
+ }
+ } else {
+ // create a single ApkData.
+ list.add(new ApkData(this, softVariants));
+ }
+
+ return list;
+ }
+
+ int getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
+ SupportsScreens getSupportsScreens() {
+ return mSupportsScreens;
+ }
+
+ int getGlEsVersion() {
+ return mGlEsVersion;
+ }
+
+ boolean isSplitByDensity() {
+ return mSplitByDensity;
+ }
+
+ boolean isSplitByAbi() {
+ return mSplitByAbi;
+ }
+
+ /**
+ * Returns a map of pair values (apk name suffix, aapt res filter) to be used to generate
+ * multiple soft apk variants.
+ */
+ private Map<String, String> computeSoftVariantMap() {
+ HashMap<String, String> map = new HashMap<String, String>();
+
+ if (mSplitByDensity && mLocaleFilters.size() > 0) {
+ for (String[] density : DENSITY_LIST) {
+ for (Entry<String,String> entry : mLocaleFilters.entrySet()) {
+ map.put(density[0] + "-" + entry.getKey(),
+ density[1] + "," + entry.getValue());
+ }
+ }
+
+ } else if (mSplitByDensity) {
+ for (String[] density : DENSITY_LIST) {
+ map.put(density[0], density[1]);
+ }
+
+ } else if (mLocaleFilters.size() > 0) {
+ map.putAll(mLocaleFilters);
+
+ }
+
+ return map;
+ }
+
+ /**
+ * Finds ABIs in a project folder. This is based on the presence of libs/<abi>/ folder.
+ *
+ * @param projectPath The OS path of the project.
+ * @return A new non-null, possibly empty, list of ABI strings.
+ */
+ private List<String> findAbis() {
+ ArrayList<String> abiList = new ArrayList<String>();
+ File libs = new File(mProjectFolder, 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;
+ }
+
+ String getConfigString(boolean onlyManifestData) {
+ StringBuilder sb = new StringBuilder();
+ LogHelper.write(sb, PROP_API, mMinSdkVersion);
+ LogHelper.write(sb, PROP_SCREENS, mSupportsScreens.getEncodedValues());
+
+ if (mGlEsVersion != ManifestData.GL_ES_VERSION_NOT_SET) {
+ LogHelper.write(sb, PROP_GL, "0x" + Integer.toHexString(mGlEsVersion));
+ }
+
+ if (onlyManifestData == false) {
+ LogHelper.write(sb, PROP_ABI, mSplitByAbi);
+ LogHelper.write(sb, PROP_DENSITY, Boolean.toString(mSplitByDensity));
+
+ if (mLocaleFilters.size() > 0) {
+ LogHelper.write(sb, PROP_LOCALEFILTERS, ApkSettings.writeLocaleFilters(mLocaleFilters));
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Compares the current project config to a list of properties.
+ * These properties are in the format output by {@link #getConfigString()}.
+ * @param values the properties to compare to.
+ * @return true if the properties exactly match the current config.
+ */
+ boolean compareToProperties(Map<String, String> values) {
+ String tmp;
+ // Note that most properties must always be present in the map.
+ try {
+ // api must always be there
+ if (mMinSdkVersion != Integer.parseInt(values.get(PROP_API))) {
+ return false;
+ }
+
+ tmp = values.get(PROP_GL); // GL is optional in the config string.
+ if (tmp != null) {
+ if (mGlEsVersion != Integer.decode(tmp)) {
+ return false;
+ }
+ }
+ } catch (NumberFormatException e) {
+ // failed to convert an integer? consider the configs not equal.
+ return false;
+ }
+
+ tmp = values.get(PROP_DENSITY);
+ if (tmp == null || mSplitByDensity != Boolean.valueOf(tmp)) {
+ return false;
+ }
+
+ tmp = values.get(PROP_ABI);
+ if (tmp == null || mSplitByAbi != Boolean.valueOf(tmp)) {
+ return false;
+ }
+
+ tmp = values.get(PROP_SCREENS);
+ if (tmp != null) {
+ SupportsScreens supportsScreens = new SupportsScreens(tmp);
+ if (supportsScreens.equals(mSupportsScreens) == false) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ tmp = values.get(PROP_LOCALEFILTERS);
+ if (tmp != null) {
+ if (mLocaleFilters.equals(ApkSettings.readLocaleFilters(tmp)) == false) {
+ return false;
+ }
+ } else {
+ // do nothing. locale filter is optional in the config string.
+ }
+
+ return true;
+ }
+}