diff options
11 files changed, 478 insertions, 59 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecTask.java b/anttasks/src/com/android/ant/AaptExecTask.java index 45adc7c..8731732 100644 --- a/anttasks/src/com/android/ant/AaptExecTask.java +++ b/anttasks/src/com/android/ant/AaptExecTask.java @@ -25,6 +25,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Task to execute aapt. @@ -95,6 +96,46 @@ public final class AaptExecTask extends SingleDependencyTask { private boolean mNonConstantId; /** + * Input path that ignores the same file 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() { + + public InputPath createPath(File file, Set<String> extensionsToCheck) { + return new ResFolderInputPath(file, extensionsToCheck); + } + }; + + /** * Sets the value of the "executable" attribute. * @param executable the value. */ @@ -351,7 +392,8 @@ public final class AaptExecTask extends SingleDependencyTask { 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")); + 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) { @@ -369,7 +411,8 @@ public final class AaptExecTask extends SingleDependencyTask { } 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*/); + List<InputPath> inputPaths = getInputPaths(paths, null /*extensionsToCheck*/, + sPathFactory); // let's not forget the manifest as an input path. if (mManifest != null) { diff --git a/anttasks/src/com/android/ant/BuildConfigTask.java b/anttasks/src/com/android/ant/BuildConfigTask.java new file mode 100644 index 0000000..08f91e9 --- /dev/null +++ b/anttasks/src/com/android/ant/BuildConfigTask.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 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.BuildConfigGenerator; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.Path; + +import java.io.IOException; + +public class BuildConfigTask extends BuildTypedTask { + + private String mGenFolder; + private String mAppPackage; + + public void setGenFolder(Path path) { + mGenFolder = TaskHelper.checkSinglePath("genFolder", path); + } + + public void setPackage(String appPackage) { + mAppPackage = appPackage; + } + + + @Override + public void execute() throws BuildException { + if (mGenFolder == null) { + throw new BuildException("Missing attribute genFolder"); + } + if (mAppPackage == null) { + throw new BuildException("Missing attribute package"); + } + + if (hasBuildTypeChanged()) { + if (isNewBuild()) { + System.out.println("Generating BuildConfig class."); + } else { + System.out.println("Build type changed: Generating new BuildConfig class."); + } + BuildConfigGenerator generator = new BuildConfigGenerator( + mGenFolder, mAppPackage, + Boolean.parseBoolean(getBuildType())); + + try { + generator.generate(); + } catch (IOException e) { + throw new BuildException("Failed to create BuildConfig class", e); + } + } else { + System.out.println("No need to generate new BuildConfig."); + } + } +} diff --git a/anttasks/src/com/android/ant/BuildTypedTask.java b/anttasks/src/com/android/ant/BuildTypedTask.java new file mode 100644 index 0000000..c697bac --- /dev/null +++ b/anttasks/src/com/android/ant/BuildTypedTask.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 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 org.apache.tools.ant.Task; + +/** + * Base class for tasks that should exec when the build type change. + */ +public abstract class BuildTypedTask extends Task { + + private String mPreviousBuildType; + private String mBuildType; + + /** Sets the current build type */ + public void setBuildType(String buildType) { + mBuildType = buildType; + } + + /** Sets the previous build type */ + public void setPreviousBuildType(String previousBuildType) { + mPreviousBuildType = previousBuildType; + } + + protected String getBuildType() { + return mBuildType; + } + + /** + * Returns if it is a new build. If the built type is not input + * from the XML, this always returns true. + * A build type is defined by having an empty previousBuildType. + */ + protected boolean isNewBuild() { + return mBuildType == null || mPreviousBuildType.length() == 0; + } + + /** + * Returns true if the build type changed. + */ + protected boolean hasBuildTypeChanged() { + // no build type? return false as the feature is simply not used + if (mBuildType == null && mPreviousBuildType == null) { + return false; + } + + return mBuildType.equals(mPreviousBuildType) == false; + } +} diff --git a/anttasks/src/com/android/ant/DependencyGraph.java b/anttasks/src/com/android/ant/DependencyGraph.java index 1885c17..8671359 100644 --- a/anttasks/src/com/android/ant/DependencyGraph.java +++ b/anttasks/src/com/android/ant/DependencyGraph.java @@ -35,6 +35,8 @@ import java.util.Set; */ public class DependencyGraph { + private final static boolean DEBUG = false; + private static enum DependencyStatus { NONE, NEW_FILE, UPDATED_FILE, MISSING_FILE, ERROR; } @@ -189,6 +191,9 @@ public class DependencyGraph { mPrereqs = new HashSet<File>(prereqs.length); for (String path : prereqs) { if (path.length() > 0) { + if (DEBUG) { + System.out.println("PREREQ: " + path); + } File f = new File(path); if (mFirstPrereq == null) { mFirstPrereq = f; @@ -290,12 +295,19 @@ public class DependencyGraph { // files to go through manually if (mPrereqs.remove(file) == false) { // turns out this is a new file! + + if (DEBUG) { + System.out.println("NEW FILE: " + file.getAbsolutePath()); + } return DependencyStatus.NEW_FILE; } else { // check the time stamp on this file if it's a file we care about based what the // input folder decides. if (inputFolder.checksForModification(file)) { if (file.lastModified() > oldestTarget) { + if (DEBUG) { + System.out.println("UPDATED FILE: " + file.getAbsolutePath()); + } return DependencyStatus.UPDATED_FILE; } } @@ -319,6 +331,9 @@ public class DependencyGraph { // Loop through our prereq files and make sure they still exist for (File prereq : mPrereqs) { if (prereq.exists() == false) { + if (DEBUG) { + System.out.println("MISSING FILE: " + prereq.getAbsolutePath()); + } return DependencyStatus.MISSING_FILE; } @@ -336,6 +351,10 @@ public class DependencyGraph { // check if we need to check this type of file, and if yes, check it. if (input.checksForModification(prereq)) { if (prereq.lastModified() > oldestTarget) { + if (DEBUG) { + System.out.println( + "UPDATED FILE: " + prereq.getAbsolutePath()); + } return DependencyStatus.UPDATED_FILE; } } @@ -345,6 +364,10 @@ public class DependencyGraph { if (prereq.equals(inputFile)) { if (input.checksForModification(prereq)) { if (prereq.lastModified() > oldestTarget) { + if (DEBUG) { + System.out.println( + "UPDATED FILE: " + prereq.getAbsolutePath()); + } return DependencyStatus.UPDATED_FILE; } } @@ -354,6 +377,9 @@ public class DependencyGraph { } else { // no input? we consider all files. if (prereq.lastModified() > oldestTarget) { + if (DEBUG) { + System.out.println("UPDATED FILE: " + prereq.getAbsolutePath()); + } return DependencyStatus.UPDATED_FILE; } } diff --git a/anttasks/src/com/android/ant/DexExecTask.java b/anttasks/src/com/android/ant/DexExecTask.java index 6be0a98..2d9479e 100644 --- a/anttasks/src/com/android/ant/DexExecTask.java +++ b/anttasks/src/com/android/ant/DexExecTask.java @@ -129,7 +129,8 @@ public class DexExecTask extends SingleDependencyTask { String depFile = mOutput + ".d"; // get InputPath with no extension restrictions - List<InputPath> inputPaths = getInputPaths(paths, null /*extensionsToCheck*/); + List<InputPath> inputPaths = getInputPaths(paths, null /*extensionsToCheck*/, + null /*factory*/); if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) { System.out.println( diff --git a/anttasks/src/com/android/ant/InputPath.java b/anttasks/src/com/android/ant/InputPath.java index 3327385..b1a98b5 100644 --- a/anttasks/src/com/android/ant/InputPath.java +++ b/anttasks/src/com/android/ant/InputPath.java @@ -72,23 +72,33 @@ public class InputPath { * @return true if the file or folder are ignored. */ public boolean ignores(File file) { - return false; + // always ignore hidden files/folders. + return file.getName().startsWith(".") == false; } /** * Gets the extension (if present) on a file by looking at the filename - * @param file the file to get the extension of + * @param file the file to get the extension from * @return the extension if present, or the empty string if the filename doesn't have * and extension. */ protected static String getExtension(File file) { - String filename = file.getName(); - int index = filename.lastIndexOf('.'); + return getExtension(file.getName()); + } + + /** + * Gets the extension (if present) on a file by looking at the filename + * @param fileName the filename to get the extension from + * @return the extension if present, or the empty string if the filename doesn't have + * and extension. + */ + protected static String getExtension(String fileName) { + int index = fileName.lastIndexOf('.'); if (index == -1) { return ""; } // Don't include the leading '.' in the extension - return filename.substring(index + 1); + return fileName.substring(index + 1); } } diff --git a/anttasks/src/com/android/ant/SingleDependencyTask.java b/anttasks/src/com/android/ant/SingleDependencyTask.java index dc3e15d..926e59c 100644 --- a/anttasks/src/com/android/ant/SingleDependencyTask.java +++ b/anttasks/src/com/android/ant/SingleDependencyTask.java @@ -17,7 +17,6 @@ package com.android.ant; import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.Task; import java.io.File; import java.io.FileNotFoundException; @@ -29,21 +28,21 @@ import java.util.Set; /** * A base class for ant tasks that use a single dependency files to control (re)execution. */ -public abstract class SingleDependencyTask extends Task { +public abstract class SingleDependencyTask extends BuildTypedTask { private DependencyGraph mDependencies; - private String mPreviousBuildType; - private String mBuildType; - public void setPreviousBuildType(String previousBuildType) { - mPreviousBuildType = previousBuildType; - } + protected abstract String getExecTaskName(); - public void setBuildType(String buildType) { - mBuildType = buildType; + protected interface InputPathFactory { + InputPath createPath(File file, Set<String> extensionsToCheck); } - protected abstract String getExecTaskName(); + private final static InputPathFactory sDefaultFactory = new InputPathFactory() { + public InputPath createPath(File file, Set<String> extensionsToCheck) { + return new InputPath(file, extensionsToCheck); + } + }; /** * Creates a list of {@link InputPath} from a list of {@link File} and an optional list of @@ -55,11 +54,15 @@ public abstract class SingleDependencyTask extends Task { * @return a list of {@link InputPath} */ protected static List<InputPath> getInputPaths(List<File> paths, - Set<String> extensionsToCheck) { + Set<String> extensionsToCheck, InputPathFactory factory) { List<InputPath> result = new ArrayList<InputPath>(paths.size()); + if (factory == null ) { + factory = sDefaultFactory; + } + for (File f : paths) { - result.add(new InputPath(f, extensionsToCheck)); + result.add(factory.createPath(f, extensionsToCheck)); } return result; @@ -73,7 +76,7 @@ public abstract class SingleDependencyTask extends Task { * @return true if the dependency graph was successfully initialized */ protected boolean initDependencies(String dependencyFile, List<InputPath> inputPaths) { - if (mBuildType != null && mBuildType.equals(mPreviousBuildType) == false) { + if (hasBuildTypeChanged()) { // we don't care about deps, we need to execute the task no matter what. return true; } @@ -93,15 +96,19 @@ public abstract class SingleDependencyTask extends Task { * have changed since the last run */ protected boolean dependenciesHaveChanged() { - if (mBuildType != null && mBuildType.equals(mPreviousBuildType) == false) { - String execName = getExecTaskName(); - if (execName == null) { - System.out.println( - "Current build type is different than previous build: forced task run."); - } else { - System.out.println( - "Current build type is different than previous build: forced " + - execName + " run."); + if (hasBuildTypeChanged()) { + // if this is not a new build, display that build type change is forcing running + // the task. + if (isNewBuild() == false) { + String execName = getExecTaskName(); + if (execName == null) { + System.out.println( + "Current build type is different than previous build: forced task run."); + } else { + System.out.println( + "Current build type is different than previous build: forced " + + execName + " run."); + } } return true; } diff --git a/files/ant/build.xml b/files/ant/build.xml index cc8f398..91dbb1f 100644 --- a/files/ant/build.xml +++ b/files/ant/build.xml @@ -7,7 +7,7 @@ regular projects, library projects, or test projects. At the beginning of the file is a list of properties that can be overridden - by adding them to your build.properties (properties are immutable, so their + by adding them to your ant.properties (properties are immutable, so their first definition sticks and is never changed). Follows: @@ -23,7 +23,9 @@ - help target --> - <!-- ********** Overrideable Properties ********** --> + <!-- ******************************************************* --> + <!-- *************** Overrideable Properties *************** --> + <!-- ******************************************************* --> <!-- You can override these values in your build.xml or build.properties. Overriding any other properties may result in broken build. --> @@ -52,7 +54,9 @@ <!-- Verbosity --> <property name="verbose" value="false" /> - <!-- ********** Custom Tasks ********** --> + <!-- ******************************************************* --> + <!-- ********************* Custom Tasks ******************** --> + <!-- ******************************************************* --> <!-- jar file from where the tasks are loaded --> <path id="android.antlibs"> @@ -76,6 +80,10 @@ classname="com.android.ant.RenderScriptTask" classpathref="android.antlibs" /> + <taskdef name="buildconfig" + classname="com.android.ant.BuildConfigTask" + classpathref="android.antlibs" /> + <taskdef name="dex" classname="com.android.ant.DexExecTask" classpathref="android.antlibs" /> @@ -106,7 +114,9 @@ <!-- End of emma configuration --> - <!-- ********** Other Properties ********** --> + <!-- ******************************************************* --> + <!-- ******************* Other Properties ****************** --> + <!-- ******************************************************* --> <!-- overriding these properties may break the build unless the whole file is updated --> @@ -174,7 +184,9 @@ <!-- properties for packaging --> <property name="build.packaging.nocrunch" value="true" /> - <!-- ********** Macros ********** --> + <!-- ******************************************************* --> + <!-- ************************ Macros *********************** --> + <!-- ******************************************************* --> <!-- macro to do a task on if project.is.library is false. elseText attribute is displayed otherwise --> @@ -244,9 +256,7 @@ <dex executable="${dx}" output="${intermediate.dex.file}" nolocals="@{nolocals}" - verbose="${verbose}" - previousBuildType="${build.last.target}" - buildType="${build.target}"> + verbose="${verbose}"> <path path="${out.dex.input.absolute.dir}"/> <path refid="out.dex.jar.input.ref" /> <external-libs /> @@ -351,7 +361,9 @@ </sequential> </macrodef> - <!-- ********** Build Targets ********** --> + <!-- ******************************************************* --> + <!-- ******************** Build Targets ******************** --> + <!-- ******************************************************* --> <!-- this target simply force running -setup making the project info be read. To be used as @@ -444,13 +456,12 @@ <!-- read the previous build mode --> <property file="${out.build.prop.file}" /> - <!-- if empty the prop won't be set, so set it to the current target - to provide a default value equal to the current build --> - <property name="build.last.target" value="${build.target}" /> - <!-- also set the default value for whether the build is instrumented --> - <property name="build.last.is.instrumented" value="${build.is.instrumented}" /> - <property name="build.last.is.packaging.debug" value="${build.is.packaging.debug}" /> - <property name="build.last.is.signing.debug" value="${build.is.signing.debug}" /> + <!-- if empty the props won't be set, meaning it's a new build. + To force a build, set the prop to empty values. --> + <property name="build.last.target" value="" /> + <property name="build.last.is.instrumented" value="" /> + <property name="build.last.is.packaging.debug" value="" /> + <property name="build.last.is.signing.debug" value="" /> <!-- compile the libraries if any --> <if> @@ -502,20 +513,39 @@ <path refid="project.libraries.jars" /> </path> - <!-- special case for instrumented: if the previous build was - instrumented but not this one, clear out the compiled code --> + <!-- If the "debug" build type changed, clear out the compiled code. + This is to make sure the new BuildConfig.DEBUG value is picked up + as javac can't deal with this type of change in its dependency computation. --> <if> <condition> <and> - <istrue value="${build.last.is.instrumented}" /> - <isfalse value="${build.is.instrumented}" /> + <length string="${build.last.is.packaging.debug}" trim="true" when="greater" length="0" /> + <not><equals + arg1="${build.is.packaging.debug}" + arg2="${build.last.is.packaging.debug}" /></not> </and> </condition> <then> - <echo>Switching from instrumented to non-instrumented build.</echo> - <echo>Deleting previous compilation output:</echo> + <echo>Switching between debug and non debug build: Deleting previous compilation output...</echo> <delete dir="${out.classes.absolute.dir}" verbose="${verbose}" /> </then> + <else> + <!-- Else, we may still need to clean the code, for another reason. + special case for instrumented: if the previous build was + instrumented but not this one, clear out the compiled code --> + <if> + <condition> + <and> + <istrue value="${build.last.is.instrumented}" /> + <isfalse value="${build.is.instrumented}" /> + </and> + </condition> + <then> + <echo>Switching from instrumented to non-instrumented build: Deleting previous compilation output...</echo> + <delete dir="${out.classes.absolute.dir}" verbose="${verbose}" /> + </then> + </if> + </else> </if> <echo>Creating output directories if needed...</echo> @@ -569,6 +599,17 @@ <res path="${out.res.absolute.dir}" /> <res path="${resource.absolute.dir}" /> </aapt> + + <echo>----------</echo> + <echo>Handling BuildConfig class...</echo> + <xpath input="AndroidManifest.xml" expression="/manifest/@package" + output="manifest.package" /> + <buildconfig + genFolder="${gen.absolute.dir}" + package="${manifest.package}" + buildType="${build.is.packaging.debug}" + previousBuildType="${build.last.is.packaging.debug}"/> + </do-only-if-manifest-hasCode> </target> @@ -593,7 +634,7 @@ </condition> <javac encoding="${java.encoding}" source="${java.source}" target="${java.target}" - debug="true" extdirs="" + debug="true" extdirs="" includeantruntime="false" destdir="${out.classes.absolute.dir}" bootclasspathref="android.target.classpath" verbose="${verbose}" @@ -817,7 +858,9 @@ message="Cannot run two different modes at the same time. If you are running more than one debug/release/instrument type targets, call them from different Ant calls." /> </target> - <!-- ********** Debug specific targets ********** --> + <!-- ******************************************************* --> + <!-- **************** Debug specific targets *************** --> + <!-- ******************************************************* --> <target name="-set-debug-files" depends="-set-mode-check"> @@ -863,7 +906,9 @@ </target> - <!-- ********** Release specific targets ********** --> + <!-- ******************************************************* --> + <!-- *************** Release specific targets ************** --> + <!-- ******************************************************* --> <!-- called through target 'release'. Only executed if the keystore and key alias are known but not their password. --> @@ -974,7 +1019,9 @@ <record-build-info /> </target> - <!-- ********** Instrumented specific targets ********** --> + <!-- ******************************************************* --> + <!-- ************ Instrumented specific targets ************ --> + <!-- ******************************************************* --> <!-- These targets are specific for the project under test when it gets compiled by the test projects in a way that will make it @@ -1001,7 +1048,9 @@ <record-build-info /> </target> - <!-- ********** Test project specific targets ********** --> + <!-- ******************************************************* --> + <!-- ************ Test project specific targets ************ --> + <!-- ******************************************************* --> <!-- enable code coverage --> <target name="emma"> @@ -1089,7 +1138,9 @@ </target> - <!-- ********** Install/uninstall specific targets ********** --> + <!-- ******************************************************* --> + <!-- ********** Install/uninstall specific targets ********* --> + <!-- ******************************************************* --> <target name="install" description="Installs the newly build package. Must be used in conjunction with a build target @@ -1107,7 +1158,7 @@ <resourceexists> <file file="${out.final.file}"/> </resourceexists> - </condition> + </condition> <then> <echo>Installing ${out.final.file} onto default emulator or device...</echo> <exec executable="${adb}" failonerror="true"> @@ -1198,6 +1249,10 @@ </target> + <!-- ******************************************************* --> + <!-- ************************* Help ************************ --> + <!-- ******************************************************* --> + <target name="help"> <!-- displays starts at col 13 |13 80| --> diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java index a475e1b..774d9f4 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java @@ -949,6 +949,8 @@ public final class ApkBuilder implements IArchiveBuilder { "class".equalsIgnoreCase(extension) == false && // Java class files "scc".equalsIgnoreCase(extension) == false && // VisualSourceSafe "swp".equalsIgnoreCase(extension) == false && // vi swap file + "thumbs.db".equalsIgnoreCase(fileName) == false && // image index file + "picasa.ini".equalsIgnoreCase(fileName) == false && // image index file "package.html".equalsIgnoreCase(fileName) == false && // Javadoc "overview.html".equalsIgnoreCase(fileName) == false; // Javadoc } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/BuildConfig.template b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/BuildConfig.template new file mode 100644 index 0000000..0344b55 --- /dev/null +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/BuildConfig.template @@ -0,0 +1,6 @@ +/** Automatically generated file. DO NOT MODIFY */ +package #PACKAGE#; + +public final class BuildConfig { + public final static boolean DEBUG = #DEBUG#; +}
\ No newline at end of file diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/BuildConfigGenerator.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/BuildConfigGenerator.java new file mode 100644 index 0000000..fb84bfd --- /dev/null +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/BuildConfigGenerator.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2011 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.build; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Class able to generate a BuildConfig class in Android project. + * The BuildConfig class contains constants related to the build target. + */ +public class BuildConfigGenerator { + + private final static String PH_PACKAGE = "#PACKAGE#"; + private final static String PH_DEBUG = "#DEBUG#"; + + private final String mGenFolder; + private final String mAppPackage; + private final boolean mDebug; + + /** + * Creates a generator + * @param genFolder the gen folder of the project + * @param appPackage the application package + * @param debug whether it's a debug build + */ + public BuildConfigGenerator(String genFolder, String appPackage, boolean debug) { + mGenFolder = genFolder; + mAppPackage = appPackage; + mDebug = debug; + } + + /** + * Generates the BuildConfig class. + */ + public void generate() throws IOException { + String template = readEmbeddedTextFile("BuildConfig.template"); + + Map<String, String> map = new HashMap<String, String>(); + map.put(PH_PACKAGE, mAppPackage); + map.put(PH_DEBUG, Boolean.toString(mDebug)); + + String content = replaceParameters(template, map); + + File genFolder = new File(mGenFolder); + File pkgFolder = new File(genFolder, mAppPackage.replaceAll("\\.", File.separator)); + if (pkgFolder.isDirectory() == false) { + pkgFolder.mkdirs(); + } + + File buildConfigJava = new File(pkgFolder, "BuildConfig.java"); + writeFile(buildConfigJava, content); + } + + /** + * Reads and returns the content of a text file embedded in the jar file. + * @param filepath the file path to the text file + * @return null if the file could not be read + * @throws IOException + */ + private String readEmbeddedTextFile(String filepath) throws IOException { + InputStream is = BuildConfigGenerator.class.getResourceAsStream(filepath); + if (is != null) { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + String line; + StringBuilder total = new StringBuilder(reader.readLine()); + while ((line = reader.readLine()) != null) { + total.append('\n'); + total.append(line); + } + + return total.toString(); + } + + // this really shouldn't happen unless the sdklib packaging is broken. + throw new IOException("BuildConfig template is missing!"); + } + + private void writeFile(File file, String content) throws IOException { + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + InputStream source = new ByteArrayInputStream(content.getBytes("UTF-8")); + + byte[] buffer = new byte[1024]; + int count = 0; + while ((count = source.read(buffer)) != -1) { + fos.write(buffer, 0, count); + } + } finally { + if (fos != null) { + fos.close(); + } + } + } + + /** + * Replaces placeholders found in a string with values. + * + * @param str the string to search for placeholders. + * @param parameters a map of <placeholder, Value> to search for in the string + * @return A new String object with the placeholder replaced by the values. + */ + private String replaceParameters(String str, Map<String, String> parameters) { + + for (Entry<String, String> entry : parameters.entrySet()) { + String value = entry.getValue(); + if (value != null) { + str = str.replaceAll(entry.getKey(), value); + } + } + + return str; + } +} |