View Javadoc
1   package com.simpligility.maven.plugins.android.standalonemojos;
2   
3   import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
4   import com.simpligility.maven.plugins.android.CommandExecutor;
5   import com.simpligility.maven.plugins.android.ExecutionException;
6   import com.simpligility.maven.plugins.android.config.ConfigHandler;
7   import com.simpligility.maven.plugins.android.config.ConfigPojo;
8   import com.simpligility.maven.plugins.android.config.PullParameter;
9   import com.simpligility.maven.plugins.android.configuration.Zipalign;
10  
11  import org.apache.commons.io.FilenameUtils;
12  import org.apache.maven.plugin.MojoExecutionException;
13  import org.apache.maven.plugin.MojoFailureException;
14  import org.apache.maven.plugins.annotations.Mojo;
15  import org.apache.maven.plugins.annotations.Parameter;
16  import org.codehaus.plexus.util.FileUtils;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import static com.simpligility.maven.plugins.android.common.AndroidExtension.APK;
24  
25  /**
26   * ZipalignMojo can run the zipalign command against the apk. Implements parsing parameters from pom or command line
27   * arguments and sets useful defaults as well.
28   *
29   * @author Manfred Moser - manfred@simpligility.com
30   */
31  @Mojo( name = "zipalign" )
32  public class ZipalignMojo extends AbstractAndroidMojo 
33  {
34  
35      /**
36       * The configuration for the zipalign goal. As soon as a zipalign goal is invoked the command will be executed
37       * unless the skip parameter is set. By default the input file is the apk produced by the build in target. The
38       * outputApk will use the postfix -aligned.apk. The following shows a default full configuration of the zipalign
39       * goal as an example for changes as plugin configuration.
40       * <pre>
41       * &lt;zipalign&gt;
42       *     &lt;skip&gt;false&lt;/skip&gt;
43       *     &lt;verbose&gt;true&lt;/verbose&gt;
44       *     &lt;inputApk&gt;${project.build.directory}/${project.finalName}.apk&lt;/inputApk&gt;
45       *     &lt;outputApk&gt;${project.build.directory}/${project.finalName}-aligned.apk&lt;/outputApk&gt;
46       * &lt;/zipalign&gt;
47       * </pre>
48       *
49       * Values can also be configured as properties on the command line as android.zipalign.*
50       * or in pom or settings file as properties like zipalign.*.
51       */
52      @Parameter
53      @ConfigPojo
54      private Zipalign zipalign;
55  
56      /**
57       * Skip the zipalign goal execution. Defaults to "true".
58       * @see com.simpligility.maven.plugins.android.configuration.Zipalign#skip
59       */
60      @Parameter( property = "android.zipalign.skip" )
61      private Boolean zipalignSkip;
62  
63      @PullParameter( defaultValue = "true" )
64      private Boolean parsedSkip;
65  
66      /**
67       * Activate verbose output for the zipalign goal execution. Defaults to "false".
68       * @see com.simpligility.maven.plugins.android.configuration.Zipalign#verbose
69       */
70      @Parameter( property = "android.zipalign.verbose" )
71      private Boolean zipalignVerbose;
72  
73      @PullParameter( defaultValue = "false" )
74      private Boolean parsedVerbose;
75  
76      /**
77       * The apk file to be zipaligned. Per default the file is taken from build directory (target normally) using the
78       * build final name as file name and apk as extension.
79       *
80       * @see com.simpligility.maven.plugins.android.configuration.Zipalign#inputApk
81       */
82      @Parameter( property = "android.zipalign.inputApk" )
83      private String zipalignInputApk;
84  
85      @PullParameter ( defaultValueGetterMethod = "getInputApkPath" )
86      private String parsedInputApk;
87  
88      /**
89       * The apk file produced by the zipalign goal. Per default the file is placed into the build directory (target
90       * normally) using the build final name appended with "-aligned" as file name and apk as extension.
91       *
92       * @see com.simpligility.maven.plugins.android.configuration.Zipalign#outputApk
93       */
94      @Parameter( property = "android.zipalign.outputApk" )
95      private String zipalignOutputApk;
96  
97      @PullParameter( defaultValueGetterMethod = "getOutputApkPath" )
98      private String parsedOutputApk;
99  
100     /**
101      * the apk file to be zipaligned.
102      */
103     private File apkFile;
104     /**
105      * the output apk file for the zipalign process.
106      */
107     private File alignedApkFile;
108 
109     /**
110      * <p>Classifier to add to the artifact generated. </p>
111      */
112     @Parameter ( property = "android.zipalign.classifier" )
113     private String zipalignClassifier;
114     
115     @PullParameter( defaultValue = "aligned" )
116     private String parsedClassifier;
117 
118     /**
119      * Execute the mojo by parsing the confign and actually doing the zipalign.
120      *
121      * @throws MojoExecutionException
122      */
123     public void execute() throws MojoExecutionException, MojoFailureException
124     {
125 
126         // If we're not on a supported packaging with just skip (Issue 87)
127         // http://code.google.com/p/maven-android-plugin/issues/detail?id=87
128         if ( ! SUPPORTED_PACKAGING_TYPES.contains( project.getPackaging() ) )
129         {
130             getLog().info( "Skipping zipalign on " + project.getPackaging() );
131             return;
132         }
133 
134         ConfigHandler configHandler = new ConfigHandler( this, this.session, this.execution );
135         configHandler.parseConfiguration();
136 
137         parsedInputApk = FilenameUtils.separatorsToSystem( parsedInputApk );
138         parsedOutputApk = FilenameUtils.separatorsToSystem( parsedOutputApk );
139 
140         getLog().debug( "skip:" + parsedSkip );
141         getLog().debug( "verbose:" + parsedVerbose );
142         getLog().debug( "inputApk:" + parsedInputApk );
143         getLog().debug( "outputApk:" + parsedOutputApk );
144         getLog().debug( "classifier:" + parsedClassifier );
145 
146         if ( parsedSkip )
147         {
148             getLog().info( "Skipping zipalign" );
149         }
150         else
151         {
152             boolean outputToSameFile = sameOutputAsInput();
153 
154             CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
155             executor.setLogger( this.getLog() );
156 
157             String command = getAndroidSdk().getZipalignPath();
158 
159             List<String> parameters = new ArrayList<String>();
160             if ( parsedVerbose )
161             {
162                 parameters.add( "-v" );
163             }
164             parameters.add( "-f" ); // force overwriting existing output file
165             parameters.add( "4" ); // byte alignment has to be 4!
166             parameters.add( parsedInputApk );
167             String outputApk = outputToSameFile ? getTemporaryOutputApkFilename() : parsedOutputApk;
168             parameters.add( outputApk );
169 
170             try
171             {
172                 getLog().info( "Running command: " + command );
173                 getLog().info( "with parameters: " + parameters );
174                 executor.setCaptureStdOut( true );
175                 executor.executeCommand( command, parameters );
176 
177                 if ( FileUtils.fileExists( outputApk ) )
178                 {
179                     if ( outputToSameFile )
180                     {
181                         // No needs to attach zipaligned apk to artifacts
182                         try
183                         {
184                             FileUtils.rename( new File( outputApk ),  new File( parsedInputApk ) );
185                         }
186                         catch ( IOException e )
187                         {
188                             getLog().error( "Failed to replace original apk with aligned "
189                                     + getFullPathWithName( outputApk ), e );
190                         }
191                     }
192                     else
193                     {
194                         // Attach the resulting artifact (Issue 88)
195                         // http://code.google.com/p/maven-android-plugin/issues/detail?id=88
196                         projectHelper.attachArtifact( project, APK, parsedClassifier, new File( outputApk ) );
197                         getLog().info( "Attach " + getFullPathWithName( outputApk )  + " as '"
198                                 + parsedClassifier + "' to the project" );
199                     }
200                 }
201                 else
202                 {
203                     getLog().error( "Cannot attach " + getFullPathWithName( outputApk ) + " to the project"
204                             + " - The file does not exist" );
205                 }
206             }
207             catch ( ExecutionException e )
208             {
209                 throw new MojoExecutionException( "", e );
210             }
211         }
212     }
213 
214     private String getFullPathWithName( String filename )
215     {
216         return FilenameUtils.getFullPath( filename ) + FilenameUtils.getName( filename );
217     }
218 
219     private boolean sameOutputAsInput()
220     {
221         return getFullPathWithName( parsedInputApk ).equals( getFullPathWithName( parsedOutputApk ) );
222     }
223 
224     // zipalign doesn't allow output file to be same as input
225     private String getTemporaryOutputApkFilename()
226     {
227         return parsedOutputApk.substring( 0, parsedOutputApk.lastIndexOf( '.' ) ) + "-aligned-temp.apk";
228     }
229 
230     /**
231      * Gets the apk file location from basedir/target/finalname.apk
232      *
233      * @return absolute path.
234      */
235     // used via PullParameter annotation - do not remove
236     private String getInputApkPath()
237     {
238         if ( apkFile == null )
239         {
240             apkFile = new File( targetDirectory, finalName + "." + APK );
241         }
242         return apkFile.getAbsolutePath();
243     }
244 
245     /**
246      * Gets the apk file location from basedir/target/finalname-aligned.apk. "-aligned" is the inserted string for the
247      * output file.
248      *
249      * @return absolute path.
250      */
251     // used via PullParameter annotation - do not remove
252     private String getOutputApkPath()
253     {
254         if ( alignedApkFile == null )
255         {
256             alignedApkFile = new File( targetDirectory,
257                     finalName + "-aligned." + APK );
258         }
259         return alignedApkFile.getAbsolutePath();
260     }
261 }