Build configurations in native Android apps using Ant
Last month I was looking at logging in native android apps and in that post I did mention that the logging configuration would need to change based on what I was trying to build. I build MeasureMe for two different reasons.
- A release build to go in the play store
In this build I want almost no logging (only if an unhandled exception is thrown. - A build for me to test with
This build has maximal logging so that if something goes wrong I get as much information as possible to help trace it.
In .NET there are any number of ways of doing this for example using #ifdef in the code. I was interested to see how this was usually done in Java. Also I needed a mechanism to build from the command line if I was going to start using some form of CI server.
Apparently the new way of doing this is to use Android Studio and Gradle, however as I have explained I was in a hurry and already using Eclipse so I went with Ant as there were lots more examples to help me.
After downloading and installing Ant then this command will add a default ant build.xml to an existing ADT project
android update project --path .
At this point we can use Ant to do compile the project just like Eclipse, so far so pointless. The standard build.xml can include custom_rules.xml and its this file that I added my tasks to.
There are any number of ways of altering build configurations but the mechanism I used is to keep template files in a folder called config and to copy and replace tokens in the files before compiling the project.
There are only two template files in the config folder one is the logging configuration file like this
<configuration debug="true"> <!-- WARNING templated file ONLY edit this file in the CONFIG folder --> <property name="LOG_HOME" value="/sdcard/MeasureMe" /> <!-- Create a logcat appender --> <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender"> <encoder> <pattern>%msg</pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_HOME}/mm_main.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>${LOG_HOME}/mm_main.%i.log</fileNamePattern> <minIndex>1</minIndex> <maxIndex>2</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>500KB</maxFileSize> </triggeringPolicy> <encoder> <pattern>%date{yyyy-MMM-dd HH:mm:ss.SSS} %t %r %logger{15} - %msg%n</pattern> </encoder> </appender> <logger name="main" level="@CONFIG.LOGLEVEL@" > <appender-ref ref="logcat" /> <appender-ref ref="FILE" /> </logger> <root level="@CONFIG.LOGLEVEL@"> <appender-ref ref="logcat" /> <appender-ref ref="FILE" /> </root> </configuration>
and the other one is a static Java method that can be used in the code to tell which configuration we are running without the need for many Java files to be templated.
package net.derekwilson.measureme; // WARNING templated file ONLY edit this file in the CONFIG folder public class MeasureMeBuildConfig { /** Whether or not to include logging statements in the application. */ public final static boolean PRODUCTION = @CONFIG.PRODUCTION@; }
We need to replace the configuration tokens @CONFIG.PRODUCTION@ and @CONFIG.LOGLEVEL@ with the correct values depending on the configuration we are trying to build and then copy the files from the config folder to their correct place in the project structure, that the is the src folder for the Java file and the assets folder for the logback XML file.
The ant build tasks to do this token substitution and copy look like this
<!-- Copy Config.java to our source tree, replacing custom tokens. --> <target name="config_java"> <property name="config-java-target-path" value="${source.dir}/net/derekwilson/measureme"/> <chmod file="${config-java-target-path}/MeasureMeBuildConfig.java" perm="+w"/> <attrib file="${config-java-target-path}/MeasureMeBuildConfig.java" readonly="false"/> <!-- Copy the configuration file, replacing tokens in the file. --> <copy file="config/MeasureMeBuildConfig.java" todir="${config-java-target-path}" overwrite="true" encoding="utf-8"> <filterset> <filter token="CONFIG.PRODUCTION" value="${config.production}"/> <filter token="CONFIG.LOGLEVEL" value="${config.loglevel}"/> </filterset> </copy> <!-- Now set it to read-only, as we don't want people accidentally editing the wrong one. NOTE: This step is unnecessary, but I do it so the developers remember that this is not the original file. --> <chmod file="${config-java-target-path}/MeasureMeBuildConfig.java" perm="-w"/> <attrib file="${config-java-target-path}/MeasureMeBuildConfig.java" readonly="true"/> </target> <!-- Copy Config.java to our source tree, replacing custom tokens.--> <target name="config_asset"> <property name="config-asset-target-path" value="./assets"/> <chmod file="${config-asset-target-path}/logback.xml" perm="+w"/> <attrib file="${config-asset-target-path}/logback.xml" readonly="false"/> <!-- Copy the configuration file, replacing tokens in the file. --> <copy file="config/logback.xml" todir="${config-asset-target-path}" overwrite="true" encoding="utf-8"> <filterset> <filter token="CONFIG.PRODUCTION" value="${config.production}"/> <filter token="CONFIG.LOGLEVEL" value="${config.loglevel}"/> </filterset> </copy> <!-- Now set it to read-only, as we don't want people accidentally editing the wrong one. NOTE: This step is unnecessary, but I do it so the developers remember that this is not the original file. --> <chmod file="${config-asset-target-path}/logback.xml" perm="-w"/> <attrib file="${config-asset-target-path}/logback.xml" readonly="true"/> </target>
Then all we need is to hook these new targets into the standard ADT Ant build target. We do that like this
<target name="dev" depends="-set-dev-props, config, -set-debug-files, -do-debug, -post-build" description="Builds the application and signs it with a debug key."> </target> <target name="prod" depends="clean, -set-prod-props, config, -set-release-mode, -release-obfuscation-check, -package, -post-package, -release-prompt-for-password, -release-nosign, -release-sign, -post-build" description="Builds the application in release mode."> </target>
I have added the set-dev-props, set-prod-props and config targets the rest of the depends are as standard. These new targets look like this.
<target name="-set-dev-props"> <property name="config.production" value="false" /> <property name="config.loglevel" value="DEBUG" /> </target> <target name="-set-prod-props"> <property name="config.production" value="true" /> <property name="config.loglevel" value="WARN" /> </target> <target name="config" depends="config_java, config_asset"> </target>
They just set the correct config variables, these are the values to be used to replace the tokens in the template files. Then we call the config_java and config_asset targets above that do the actual copying.
This template mechanism is only performed when I run Ant from the command line, either “ant dev” or “ant prod”. It is never performed from inside eclipse, however if Eclipse is open when we run Ant then it can get confused and require the project to be refreshed or you may need to exit Eclipse and reopen the project.