| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ant; |
| |
| import com.android.sdklib.internal.build.SymbolLoader; |
| import com.android.sdklib.internal.build.SymbolWriter; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.taskdefs.ExecTask; |
| import org.apache.tools.ant.types.Path; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Task to execute aapt. |
| * |
| * <p>It does not follow the exec task format, instead it has its own parameters, which maps |
| * directly to aapt.</p> |
| * <p>It is able to run aapt several times if library setup requires generating several |
| * R.java files. |
| * <p>The following map shows how to use the task for each supported aapt command line |
| * parameter.</p> |
| * |
| * <table border="1"> |
| * <tr><td><b>Aapt Option</b></td><td><b>Ant Name</b></td><td><b>Type</b></td></tr> |
| * <tr><td>path to aapt</td><td>executable</td><td>attribute (Path)</td> |
| * <tr><td>command</td><td>command</td><td>attribute (String)</td> |
| * <tr><td>-v</td><td>verbose</td><td>attribute (boolean)</td></tr> |
| * <tr><td>-f</td><td>force</td><td>attribute (boolean)</td></tr> |
| * <tr><td>-M AndroidManifest.xml</td><td>manifest</td><td>attribute (Path)</td></tr> |
| * <tr><td>-I base-package</td><td>androidjar</td><td>attribute (Path)</td></tr> |
| * <tr><td>-A asset-source-dir</td><td>assets</td><td>attribute (Path</td></tr> |
| * <tr><td>-S resource-sources</td><td><res path=""></td><td>nested element(s)<br>with attribute (Path)</td></tr> |
| * <tr><td>-0 extension</td><td><nocompress extension=""><br><nocompress></td><td>nested element(s)<br>with attribute (String)</td></tr> |
| * <tr><td>-F apk-file</td><td>apkfolder<br>outfolder<br>apkbasename<br>basename</td><td>attribute (Path)<br>attribute (Path) deprecated<br>attribute (String)<br>attribute (String) deprecated</td></tr> |
| * <tr><td>-J R-file-dir</td><td>rfolder</td><td>attribute (Path)<br>-m always enabled</td></tr> |
| * <tr><td>--rename-manifest-package package-name</td><td>manifestpackage</td><td>attribute (String)</td></tr> |
| * <tr><td></td><td></td><td></td></tr> |
| * </table> |
| */ |
| public final class AaptExecTask extends SingleDependencyTask { |
| |
| /** |
| * Class representing a <nocompress> node in the main task XML. |
| * This let the developers prevent compression of some files in assets/ and res/raw/ |
| * by extension. |
| * If the extension is null, this will disable compression for all files in assets/ and |
| * res/raw/ |
| */ |
| public final static class NoCompress { |
| String mExtension; |
| |
| /** |
| * Sets the value of the "extension" attribute. |
| * @param extention the extension. |
| */ |
| public void setExtension(String extention) { |
| mExtension = extention; |
| } |
| } |
| |
| private String mExecutable; |
| private String mCommand; |
| private boolean mForce = true; // true due to legacy reasons |
| private boolean mDebug = false; |
| private boolean mVerbose = false; |
| private boolean mUseCrunchCache = false; |
| private int mVersionCode = 0; |
| private String mVersionName; |
| private String mManifest; |
| private String mManifestPackage; |
| private ArrayList<Path> mResources; |
| private String mAssets; |
| private String mAndroidJar; |
| private String mApkFolder; |
| private String mApkName; |
| private String mResourceFilter; |
| private String mRFolder; |
| private final ArrayList<NoCompress> mNoCompressList = new ArrayList<NoCompress>(); |
| private String mLibraryResFolderPathRefid; |
| private String mLibraryPackagesRefid; |
| private String mLibraryRFileRefid; |
| private boolean mNonConstantId; |
| private String mIgnoreAssets; |
| private String mBinFolder; |
| private String mProguardFile; |
| |
| /** |
| * Input path that ignores the same folders/files that aapt does. |
| */ |
| private static class ResFolderInputPath extends InputPath { |
| public ResFolderInputPath(File file, Set<String> extensionsToCheck) { |
| super(file, extensionsToCheck); |
| } |
| |
| @Override |
| public boolean ignores(File file) { |
| String name = file.getName(); |
| char firstChar = name.charAt(0); |
| |
| if (firstChar == '.' || (firstChar == '_' && file.isDirectory()) || |
| name.charAt(name.length()-1) == '~') { |
| return true; |
| } |
| |
| if ("CVS".equals(name) || |
| "thumbs.db".equalsIgnoreCase(name) || |
| "picasa.ini".equalsIgnoreCase(name)) { |
| return true; |
| } |
| |
| String ext = getExtension(name); |
| if ("scc".equalsIgnoreCase(ext)) { |
| return true; |
| } |
| |
| return false; |
| } |
| } |
| |
| private final static InputPathFactory sPathFactory = new InputPathFactory() { |
| |
| @Override |
| public InputPath createPath(File file, Set<String> extensionsToCheck) { |
| return new ResFolderInputPath(file, extensionsToCheck); |
| } |
| }; |
| |
| /** |
| * Sets the value of the "executable" attribute. |
| * @param executable the value. |
| */ |
| public void setExecutable(Path executable) { |
| mExecutable = TaskHelper.checkSinglePath("executable", executable); |
| } |
| |
| /** |
| * Sets the value of the "command" attribute. |
| * @param command the value. |
| */ |
| public void setCommand(String command) { |
| mCommand = command; |
| } |
| |
| /** |
| * Sets the value of the "force" attribute. |
| * @param force the value. |
| */ |
| public void setForce(boolean force) { |
| mForce = force; |
| } |
| |
| /** |
| * Sets the value of the "verbose" attribute. |
| * @param verbose the value. |
| */ |
| public void setVerbose(boolean verbose) { |
| mVerbose = verbose; |
| } |
| |
| /** |
| * Sets the value of the "usecrunchcache" attribute |
| * @param usecrunch whether to use the crunch cache. |
| */ |
| public void setNoCrunch(boolean nocrunch) { |
| mUseCrunchCache = nocrunch; |
| } |
| |
| public void setNonConstantId(boolean nonConstantId) { |
| mNonConstantId = nonConstantId; |
| } |
| |
| public void setIgnoreAssets(String ignoreAssets) { |
| mIgnoreAssets = ignoreAssets; |
| } |
| |
| public void setVersioncode(String versionCode) { |
| if (versionCode.length() > 0) { |
| try { |
| mVersionCode = Integer.decode(versionCode); |
| } catch (NumberFormatException e) { |
| System.out.println(String.format( |
| "WARNING: Ignoring invalid version code value '%s'.", versionCode)); |
| } |
| } |
| } |
| |
| /** |
| * Sets the value of the "versionName" attribute |
| * @param versionName the value |
| */ |
| public void setVersionname(String versionName) { |
| mVersionName = versionName; |
| } |
| |
| public void setDebug(boolean value) { |
| mDebug = value; |
| } |
| |
| /** |
| * Sets the value of the "manifest" attribute. |
| * @param manifest the value. |
| */ |
| public void setManifest(Path manifest) { |
| mManifest = TaskHelper.checkSinglePath("manifest", manifest); |
| } |
| |
| /** |
| * Sets a custom manifest package ID to be used during packaging.<p> |
| * The manifest will be rewritten so that its package ID becomes the value given here. |
| * Relative class names in the manifest (e.g. ".Foo") will be rewritten to absolute names based |
| * on the existing package name, meaning that no code changes need to be made. |
| * |
| * @param packageName The package ID the APK should have. |
| */ |
| public void setManifestpackage(String packageName) { |
| if (packageName != null && packageName.length() != 0) { |
| mManifestPackage = packageName; |
| } |
| } |
| |
| /** |
| * Sets the value of the "resources" attribute. |
| * @param resources the value. |
| * |
| * @deprecated Use nested element(s) <res path="value" /> |
| */ |
| @Deprecated |
| public void setResources(Path resources) { |
| System.out.println("WARNNG: Using deprecated 'resources' attribute in AaptExecLoopTask." + |
| "Use nested element(s) <res path=\"value\" /> instead."); |
| if (mResources == null) { |
| mResources = new ArrayList<Path>(); |
| } |
| |
| mResources.add(new Path(getProject(), resources.toString())); |
| } |
| |
| /** |
| * Sets the value of the "assets" attribute. |
| * @param assets the value. |
| */ |
| public void setAssets(Path assets) { |
| mAssets = TaskHelper.checkSinglePath("assets", assets); |
| } |
| |
| /** |
| * Sets the value of the "androidjar" attribute. |
| * @param androidJar the value. |
| */ |
| public void setAndroidjar(Path androidJar) { |
| mAndroidJar = TaskHelper.checkSinglePath("androidjar", androidJar); |
| } |
| |
| /** |
| * Sets the value of the "outfolder" attribute. |
| * @param outFolder the value. |
| * @deprecated use {@link #setApkfolder(Path)} |
| */ |
| @Deprecated |
| public void setOutfolder(Path outFolder) { |
| System.out.println("WARNNG: Using deprecated 'outfolder' attribute in AaptExecLoopTask." + |
| "Use 'apkfolder' (path) instead."); |
| mApkFolder = TaskHelper.checkSinglePath("outfolder", outFolder); |
| } |
| |
| /** |
| * Sets the value of the "apkfolder" attribute. |
| * @param apkFolder the value. |
| */ |
| public void setApkfolder(Path apkFolder) { |
| mApkFolder = TaskHelper.checkSinglePath("apkfolder", apkFolder); |
| } |
| |
| /** |
| * Sets the value of the resourcefilename attribute |
| * @param apkName the value |
| */ |
| public void setResourcefilename(String apkName) { |
| mApkName = apkName; |
| } |
| |
| /** |
| * Sets the value of the "rfolder" attribute. |
| * @param rFolder the value. |
| */ |
| public void setRfolder(Path rFolder) { |
| mRFolder = TaskHelper.checkSinglePath("rfolder", rFolder); |
| } |
| |
| public void setresourcefilter(String filter) { |
| if (filter != null && filter.length() > 0) { |
| mResourceFilter = filter; |
| } |
| } |
| |
| /** |
| * Set the property name of the property that contains the list of res folder for |
| * Library Projects. This sets the name and not the value itself to handle the case where |
| * it doesn't exist. |
| * @param projectLibrariesResName |
| */ |
| public void setLibraryResFolderPathRefid(String libraryResFolderPathRefid) { |
| mLibraryResFolderPathRefid = libraryResFolderPathRefid; |
| } |
| |
| public void setLibraryPackagesRefid(String libraryPackagesRefid) { |
| mLibraryPackagesRefid = libraryPackagesRefid; |
| } |
| |
| public void setLibraryRFileRefid(String libraryRFileRefid) { |
| mLibraryRFileRefid = libraryRFileRefid; |
| } |
| |
| public void setBinFolder(Path binFolder) { |
| mBinFolder = TaskHelper.checkSinglePath("binFolder", binFolder); |
| } |
| |
| public void setProguardFile(Path proguardFile) { |
| mProguardFile = TaskHelper.checkSinglePath("proguardFile", proguardFile); |
| } |
| |
| /** |
| * Returns an object representing a nested <var>nocompress</var> element. |
| */ |
| public Object createNocompress() { |
| NoCompress nc = new NoCompress(); |
| mNoCompressList.add(nc); |
| return nc; |
| } |
| |
| /** |
| * Returns an object representing a nested <var>res</var> element. |
| */ |
| public Object createRes() { |
| if (mResources == null) { |
| mResources = new ArrayList<Path>(); |
| } |
| |
| Path path = new Path(getProject()); |
| mResources.add(path); |
| |
| return path; |
| } |
| |
| @Override |
| protected String getExecTaskName() { |
| return "aapt"; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * Executes the loop. Based on the values inside project.properties, this will |
| * create alternate temporary ap_ files. |
| * |
| * @see org.apache.tools.ant.Task#execute() |
| */ |
| @Override |
| public void execute() throws BuildException { |
| if (mLibraryResFolderPathRefid == null) { |
| throw new BuildException("Missing attribute libraryResFolderPathRefid"); |
| } |
| if (mLibraryPackagesRefid == null) { |
| throw new BuildException("Missing attribute libraryPackagesRefid"); |
| } |
| if (mLibraryRFileRefid == null) { |
| throw new BuildException("Missing attribute libraryRFileRefid"); |
| } |
| |
| Project taskProject = getProject(); |
| |
| String libPkgProp = null; |
| Path libRFileProp = null; |
| |
| // if the parameters indicate generation of the R class, check if |
| // more R classes need to be created for libraries, only if this project itself |
| // is not a library |
| if (mNonConstantId == false && mRFolder != null && new File(mRFolder).isDirectory()) { |
| libPkgProp = taskProject.getProperty(mLibraryPackagesRefid); |
| Object rFilePath = taskProject.getReference(mLibraryRFileRefid); |
| |
| // if one is null, both should be |
| if ((libPkgProp == null || rFilePath == null) && |
| rFilePath != libPkgProp) { |
| throw new BuildException(String.format( |
| "Both %1$s and %2$s should resolve to valid values.", |
| mLibraryPackagesRefid, mLibraryRFileRefid)); |
| } |
| |
| if (rFilePath instanceof Path) { |
| libRFileProp = (Path) rFilePath; |
| } else if (rFilePath != null) { |
| throw new BuildException("attribute libraryRFileRefid must reference a Path object."); |
| } |
| } |
| |
| final boolean generateRClass = mRFolder != null && new File(mRFolder).isDirectory(); |
| |
| // Get whether we have libraries |
| Object libResRef = taskProject.getReference(mLibraryResFolderPathRefid); |
| |
| // Set up our input paths that matter for dependency checks |
| ArrayList<File> paths = new ArrayList<File>(); |
| |
| // the project res folder is an input path of course |
| for (Path pathList : mResources) { |
| for (String path : pathList.list()) { |
| paths.add(new File(path)); |
| } |
| } |
| |
| // and if libraries exist, their res folders folders too. |
| if (libResRef instanceof Path) { |
| for (String path : ((Path)libResRef).list()) { |
| paths.add(new File(path)); |
| } |
| } |
| |
| // Now we figure out what we need to do |
| if (generateRClass) { |
| // in this case we only want to run aapt if an XML file was touched, or if any |
| // file is added/removed |
| List<InputPath> inputPaths = getInputPaths(paths, Collections.singleton("xml"), |
| sPathFactory); |
| |
| // let's not forget the manifest as an input path (with no extension restrictions). |
| if (mManifest != null) { |
| inputPaths.add(new InputPath(new File(mManifest))); |
| } |
| |
| // Check to see if our dependencies have changed. If not, then skip |
| if (initDependencies(mRFolder + File.separator + "R.java.d", inputPaths) |
| && dependenciesHaveChanged() == false) { |
| System.out.println("No changed resources. R.java and Manifest.java untouched."); |
| return; |
| } else { |
| System.out.println("Generating resource IDs..."); |
| } |
| } else { |
| // in this case we want to run aapt if any file was updated/removed/added in any of the |
| // input paths |
| List<InputPath> inputPaths = getInputPaths(paths, null /*extensionsToCheck*/, |
| sPathFactory); |
| |
| // let's not forget the manifest as an input path. |
| if (mManifest != null) { |
| inputPaths.add(new InputPath(new File(mManifest))); |
| } |
| |
| // If we're here to generate a .ap_ file we need to use assets as an input path as well. |
| if (mAssets != null) { |
| File assetsDir = new File(mAssets); |
| if (assetsDir.isDirectory()) { |
| inputPaths.add(new InputPath(assetsDir)); |
| } |
| } |
| |
| // Find our dependency file. It should have the same name as our target .ap_ but |
| // with a .d extension |
| String dependencyFilePath = mApkFolder + File.separator + mApkName; |
| dependencyFilePath += ".d"; |
| |
| // Check to see if our dependencies have changed |
| if (initDependencies(dependencyFilePath, inputPaths) |
| && dependenciesHaveChanged() == false) { |
| System.out.println("No changed resources or assets. " + mApkName |
| + " remains untouched"); |
| return; |
| } |
| if (mResourceFilter == null) { |
| System.out.println("Creating full resource package..."); |
| } else { |
| System.out.println(String.format( |
| "Creating resource package with filter: (%1$s)...", |
| mResourceFilter)); |
| } |
| } |
| |
| // create a task for the default apk. |
| ExecTask task = new ExecTask(); |
| task.setExecutable(mExecutable); |
| task.setFailonerror(true); |
| |
| task.setTaskName(getExecTaskName()); |
| |
| // aapt command. Only "package" is supported at this time really. |
| task.createArg().setValue(mCommand); |
| |
| // No crunch flag |
| if (mUseCrunchCache) { |
| task.createArg().setValue("--no-crunch"); |
| } |
| |
| if (mNonConstantId) { |
| task.createArg().setValue("--non-constant-id"); |
| } |
| |
| // force flag |
| if (mForce) { |
| task.createArg().setValue("-f"); |
| } |
| |
| // verbose flag |
| if (mVerbose) { |
| task.createArg().setValue("-v"); |
| } |
| |
| if (mDebug) { |
| task.createArg().setValue("--debug-mode"); |
| } |
| |
| if (generateRClass) { |
| task.createArg().setValue("-m"); |
| } |
| |
| // filters if needed |
| if (mResourceFilter != null && mResourceFilter.length() > 0) { |
| task.createArg().setValue("-c"); |
| task.createArg().setValue(mResourceFilter); |
| } |
| |
| // no compress flag |
| // first look to see if there's a NoCompress object with no specified extension |
| boolean compressNothing = false; |
| for (NoCompress nc : mNoCompressList) { |
| if (nc.mExtension == null) { |
| task.createArg().setValue("-0"); |
| task.createArg().setValue(""); |
| compressNothing = true; |
| break; |
| } |
| } |
| |
| if (compressNothing == false) { |
| for (NoCompress nc : mNoCompressList) { |
| task.createArg().setValue("-0"); |
| task.createArg().setValue(nc.mExtension); |
| } |
| } |
| |
| // if this is a library or there are library dependencies |
| if (mNonConstantId || (libPkgProp != null && libPkgProp.length() > 0)) { |
| if (mBinFolder == null) { |
| throw new BuildException( |
| "Missing attribute binFolder when compiling libraries or projects with libraries."); |
| } |
| task.createArg().setValue("--output-text-symbols"); |
| task.createArg().setValue(mBinFolder); |
| } |
| |
| // if the project contains libraries, force auto-add-overlay |
| if (libResRef != null) { |
| task.createArg().setValue("--auto-add-overlay"); |
| } |
| |
| if (mVersionCode != 0) { |
| task.createArg().setValue("--version-code"); |
| task.createArg().setValue(Integer.toString(mVersionCode)); |
| } |
| |
| if (mVersionName != null && mVersionName.length() > 0) { |
| task.createArg().setValue("--version-name"); |
| task.createArg().setValue(mVersionName); |
| } |
| |
| // manifest location |
| if (mManifest != null && mManifest.length() > 0) { |
| task.createArg().setValue("-M"); |
| task.createArg().setValue(mManifest); |
| } |
| |
| // Rename manifest package |
| if (mManifestPackage != null) { |
| task.createArg().setValue("--rename-manifest-package"); |
| task.createArg().setValue(mManifestPackage); |
| } |
| |
| // resources locations. |
| if (mResources.size() > 0) { |
| for (Path pathList : mResources) { |
| for (String path : pathList.list()) { |
| // This may not exists, and aapt doesn't like it, so we check first. |
| File res = new File(path); |
| if (res.isDirectory()) { |
| task.createArg().setValue("-S"); |
| task.createArg().setValue(path); |
| } |
| } |
| } |
| } |
| |
| // add other resources coming from library project |
| if (libResRef instanceof Path) { |
| for (String path : ((Path)libResRef).list()) { |
| // This may not exists, and aapt doesn't like it, so we check first. |
| File res = new File(path); |
| if (res.isDirectory()) { |
| task.createArg().setValue("-S"); |
| task.createArg().setValue(path); |
| } |
| } |
| } |
| |
| // assets location. This may not exists, and aapt doesn't like it, so we check first. |
| if (mAssets != null && new File(mAssets).isDirectory()) { |
| task.createArg().setValue("-A"); |
| task.createArg().setValue(mAssets); |
| } |
| |
| // android.jar |
| if (mAndroidJar != null) { |
| task.createArg().setValue("-I"); |
| task.createArg().setValue(mAndroidJar); |
| } |
| |
| // apk file. This is based on the apkFolder, apkBaseName, and the configName (if applicable) |
| String filename = null; |
| if (mApkName != null) { |
| filename = mApkName; |
| } |
| |
| if (filename != null) { |
| File file = new File(mApkFolder, filename); |
| task.createArg().setValue("-F"); |
| task.createArg().setValue(file.getAbsolutePath()); |
| } |
| |
| // R class generation |
| if (generateRClass) { |
| task.createArg().setValue("-J"); |
| task.createArg().setValue(mRFolder); |
| } |
| |
| // ignore assets flag |
| if (mIgnoreAssets != null && mIgnoreAssets.length() > 0) { |
| task.createArg().setValue("--ignore-assets"); |
| task.createArg().setValue(mIgnoreAssets); |
| } |
| |
| // Use dependency generation |
| task.createArg().setValue("--generate-dependencies"); |
| |
| // use the proguard file |
| if (mProguardFile != null && mProguardFile.length() > 0) { |
| task.createArg().setValue("-G"); |
| task.createArg().setValue(mProguardFile); |
| } |
| |
| // final setup of the task |
| task.setProject(taskProject); |
| task.setOwningTarget(getOwningTarget()); |
| |
| // execute it. |
| task.execute(); |
| |
| // now if the project has libraries, R needs to be created for each libraries |
| // but only if the project is not a library. |
| try { |
| if (!mNonConstantId && libPkgProp != null && !libPkgProp.isEmpty()) { |
| SymbolLoader symbolValues = new SymbolLoader(new File(mBinFolder, "R.txt")); |
| symbolValues.load(); |
| |
| // we have two props which contains list of items. Both items reprensent 2 data of |
| // a single property. |
| // Don't want to use guava's splitter because it doesn't provide a list of the |
| // result. but we know the list starts with a ; so strip it. |
| if (libPkgProp.startsWith(";")) { |
| libPkgProp = libPkgProp.substring(1).trim(); |
| } |
| String[] packages = libPkgProp.split(";"); |
| String[] rFiles = libRFileProp.list(); |
| |
| if (packages.length != rFiles.length) { |
| throw new BuildException(String.format( |
| "%1$s and %2$s must contain the same number of items.", |
| mLibraryPackagesRefid, mLibraryRFileRefid)); |
| } |
| |
| for (int i = 0 ; i < packages.length ; i++) { |
| SymbolLoader symbols = new SymbolLoader(new File(rFiles[i])); |
| symbols.load(); |
| |
| SymbolWriter writer = new SymbolWriter(mRFolder, packages[i], |
| symbols, symbolValues); |
| writer.write(); |
| } |
| } |
| } catch (IOException e) { |
| throw new BuildException(e); |
| } |
| |
| } |
| } |