View Javadoc
1   package com.simpligility.maven.plugins.android.standalonemojos;
2   
3   import com.android.SdkConstants;
4   import com.android.builder.core.AndroidBuilder;
5   import com.android.builder.core.ErrorReporter;
6   import com.android.builder.dependency.level2.AndroidDependency;
7   import com.android.ide.common.process.DefaultProcessExecutor;
8   import com.android.manifmerger.ManifestMerger2;
9   import com.android.utils.ILogger;
10  import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
11  import com.simpligility.maven.plugins.android.DefaultJavaProcessExecutor;
12  import com.simpligility.maven.plugins.android.MavenErrorReporter;
13  import com.simpligility.maven.plugins.android.common.AndroidExtension;
14  import com.simpligility.maven.plugins.android.configuration.ManifestMerger;
15  import com.simpligility.maven.plugins.android.configuration.UsesSdk;
16  import com.simpligility.maven.plugins.android.configuration.VersionGenerator;
17  import com.simpligility.maven.plugins.android.phase01generatesources.MavenILogger;
18  
19  import org.apache.commons.lang3.StringUtils;
20  import org.apache.maven.artifact.Artifact;
21  import org.apache.maven.plugin.MojoExecutionException;
22  import org.apache.maven.plugin.MojoFailureException;
23  import org.apache.maven.plugins.annotations.LifecyclePhase;
24  import org.apache.maven.plugins.annotations.Mojo;
25  import org.apache.maven.plugins.annotations.Parameter;
26  
27  import java.io.File;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Set;
32  
33  /**
34   * Manifest Merger V2 <code>AndroidManifest.xml</code> file.
35   * http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger
36   *
37   * @author Benoit Billington - benoit.billington@gmail.com
38   */
39  @Mojo( name = "manifest-merger", defaultPhase = LifecyclePhase.PROCESS_RESOURCES )
40  public class ManifestMergerMojo extends AbstractAndroidMojo
41  {
42  
43      /**
44       * Configuration for the manifest-update goal.
45       * <p>
46       * You can configure this mojo to update the following basic manifestMerger attributes:
47       * </p>
48       * <p>
49       * <code>android:versionName</code> on the <code>manifestMerger</code> element.
50       * <code>android:versionCode</code> on the <code>manifestMerger</code> element.
51       * </p>
52       * <p>
53       * You can configure attributes in the plugin configuration like so
54       * 
55       * <pre>
56       *   &lt;plugin&gt;
57       *     &lt;groupId&gt;com.jayway.maven.plugins.android.generation2&lt;/groupId&gt;
58       *     &lt;artifactId&gt;android-maven-plugin&lt;/artifactId&gt;
59       *     &lt;executions&gt;
60       *       &lt;execution&gt;
61       *         &lt;id&gt;merge-manifest&lt;/id&gt;
62       *         &lt;goals&gt;
63       *           &lt;goal&gt;manifest-merger&lt;/goal&gt;
64       *         &lt;/goals&gt;
65       *         &lt;configuration&gt;
66       *           &lt;manifestMerger&gt;
67       *             &lt;versionName&gt;&lt;/versionName&gt;
68       *             &lt;versionCode&gt;123&lt;/versionCode&gt;
69       *             &lt;versionCodeUpdateFromVersion&gt;true|false&lt;/versionCodeUpdateFromVersion&gt;
70       *             &lt;versionNamingPattern&gt;&lt;/versionNamingPattern&gt;
71       *             &lt;mergeLibraries&gt;true|false&lt;/mergeLibraries&gt;
72       *             &lt;mergeReportFile&gt;${project.build.directory}/ManifestMergeReport.txt&lt;/mergeReportFile&gt;
73       *             &lt;usesSdk&gt;
74       *               &lt;minSdkVersion&gt;14&lt;/minSdkVersion&gt;
75       *               &lt;targetSdkVersion&gt;21&lt;/targetSdkVersion&gt;
76       *             &lt;/usesSdk&gt;
77       *           &lt;/manifestMerger&gt;
78       *         &lt;/configuration&gt;
79       *       &lt;/execution&gt;
80       *     &lt;/executions&gt;
81       *   &lt;/plugin&gt;
82       * </pre>
83       * 
84       * or use properties set in the pom or settings file or supplied as command line parameter. Add
85       * "android." in front of the property name for command line usage. All parameters follow a
86       * manifestMerger.* naming convention.
87       * 
88       */
89      @Parameter
90      private ManifestMerger manifestMerger;
91  
92      /**
93       * Update the <code>android:versionName</code> with the specified parameter. If left empty it
94       * will use the version number of the project. Exposed via the project property
95       * <code>android.manifestMerger.versionName</code>.
96       */
97      @Parameter( property = "android.manifestMerger.versionName", defaultValue = "${project.version}" )
98      protected String manifestVersionName;
99  
100     /**
101      * Update the <code>android:versionCode</code> attribute with the specified parameter. Exposed via
102      * the project property <code>android.manifestMerger.versionCode</code>.
103      */
104     @Parameter( property = "android.manifestMerger.versionCode", defaultValue = "1" )
105     protected Integer manifestVersionCode;
106 
107     /**
108      * Update the <code>android:versionCode</code> attribute automatically from the project version
109      * e.g 3.2.1 will become version code 3002001. As described in this blog post
110      * http://www.simpligility.com/2010/11/release-version-management-for-your-android-application/
111      * but done without using resource filtering. The value is exposed via the project property
112      * property <code>android.manifest.versionCodeUpdateFromVersion</code> and the resulting value
113      * as <code>android.manifest.versionCode</code>.
114      * For the purpose of generating the versionCode, if a version element is missing it is presumed to be 0.
115      * The maximum values for the version increment and version minor values are 999,
116      * the version major should be no larger than 2000.  Any other suffixes do not
117      * participate in the version code generation.
118      */
119     @Parameter( property = "android.manifest.versionCodeUpdateFromVersion", defaultValue = "false" )
120     protected Boolean manifestVersionCodeUpdateFromVersion = false;
121 
122     /**
123      * Optionally use a pattern to match version elements for automatic generation of version codes,
124      * useful in case of complex version naming schemes. The new behavior is disabled by default;
125      * set the pattern to a non-empty string to activate. Otherwise, continue using the old
126      * behavior of separating version elements by dots and ignoring all non-digit characters.
127      * The pattern is standard Java regex. Capturing groups in the pattern are sequentially passed
128      * to the version code generator, while other parts are ignored. Be sure to properly escape
129      * your pattern string, in case you use characters that have special meaning in XML.
130      * Exposed via the project property
131      * <code>android.manifestMerger.versionNamingPattern</code>.
132      */
133     @Parameter( property = "android.manifestMerger.versionNamingPattern" )
134     protected String manifestVersionNamingPattern;
135 
136     /**
137      * The number of digits per version element. Must be specified as a comma/semicolon separated list of
138      * digits, one for each version element, Exposed via the project property
139      * <code>android.manifestMerger.versionDigits</code>.
140      */
141     @Parameter( property = "android.manifestMerger.versionDigits", defaultValue = "4,3,3" )
142     protected String manifestVersionDigits;
143 
144     /**
145      * Merge Manifest with library projects. Exposed via the project property
146      * <code>android.manifestMerger.mergeLibraries</code>.
147      */
148     @Parameter( property = "android.manifestMerger.mergeLibraries", defaultValue = "false" )
149     protected Boolean manifestMergeLibraries;
150 
151     /**
152      * Merge Manifest with library projects. Exposed via the project property
153      * <code>android.manifestMerger.mergeLibraries</code>.
154      */
155     @Parameter( property = "android.manifestMerger.mergeReportFile" )
156     protected File manifestMergeReportFile;
157 
158     /**
159      *  Update the uses-sdk tag. It can be configured to change: <code>android:minSdkVersion</code>,
160      *  <code>android:maxSdkVersion</code> and <code>android:targetSdkVersion</code>
161      */
162     protected UsesSdk manifestUsesSdk;
163     private Boolean parsedVersionCodeUpdateFromVersion;
164     private String parsedVersionNamingPattern;
165     private String parsedVersionDigits;
166     private Boolean parsedMergeLibraries;
167     private String parsedVersionName;
168     private Integer parsedVersionCode;
169     private UsesSdk parsedUsesSdk;
170     private File parsedMergeReportFile;
171 
172     /**
173      * @throws org.apache.maven.plugin.MojoExecutionException
174      * @throws org.apache.maven.plugin.MojoFailureException
175      */
176     public void execute() throws MojoExecutionException, MojoFailureException
177     {
178         if ( ! AndroidExtension.isAndroidPackaging( project.getPackaging() ) )
179         {
180             return; // skip, not an android project.
181         }
182 
183         if ( androidManifestFile == null )
184         {
185             getLog().debug( "skip, no androidmanifest.xml defined (androidManifestFile rare case)" );
186             return; // skip, no androidmanifest.xml defined (rare case)
187         }
188 
189         parseConfiguration();
190 
191         getLog().info( "Attempting to update manifest " + androidManifestFile );
192         getLog().debug( "    usesSdk=" + parsedUsesSdk );
193         getLog().debug( "    versionName=" + parsedVersionName );
194         getLog().debug( "    versionCode=" + parsedVersionCode );
195         getLog().debug( "    usesSdk=" + parsedUsesSdk );
196         getLog().debug( "    versionCodeUpdateFromVersion=" + parsedVersionCodeUpdateFromVersion );
197         getLog().debug( "    versionNamingPattern=" + parsedVersionNamingPattern );
198         getLog().debug( "    versionDigits=" + parsedVersionDigits );
199         getLog().debug( "    mergeLibraries=" + parsedMergeLibraries );
200         getLog().debug( "    mergeReportFile=" + parsedMergeReportFile );
201 
202         if ( ! androidManifestFile.exists() )
203         {
204             return; // skip, no AndroidManifest.xml file found.
205         }
206 
207         getLog().debug( "Using manifest merger V2" );
208         manifestMergerV2();
209     }
210 
211     private void parseConfiguration()
212     {
213         // manifestMerger element found in plugin config in pom
214         if ( manifestMerger != null )
215         {
216             if ( StringUtils.isNotEmpty( manifestMerger.getVersionName() ) )
217             {
218                 parsedVersionName = manifestMerger.getVersionName();
219             }
220             else
221             {
222                 parsedVersionName = manifestVersionName;
223             }
224             if ( manifestMerger.getVersionCode() != null )
225             {
226                 parsedVersionCode = manifestMerger.getVersionCode();
227             }
228             else
229             {
230                 parsedVersionCode = manifestVersionCode;
231             }
232             if ( manifestMerger.getVersionCodeUpdateFromVersion() != null )
233             {
234                 parsedVersionCodeUpdateFromVersion = manifestMerger.getVersionCodeUpdateFromVersion();
235             }
236             else
237             {
238                 parsedVersionCodeUpdateFromVersion = manifestVersionCodeUpdateFromVersion;
239             }
240             if ( manifestMerger.getVersionNamingPattern() != null )
241             {
242                 parsedVersionNamingPattern = manifestMerger.getVersionNamingPattern();
243             }
244             else
245             {
246                 parsedVersionNamingPattern = manifestVersionNamingPattern;
247             }
248             if ( manifestMerger.getVersionDigits() != null )
249             {
250                 parsedVersionDigits = manifestMerger.getVersionDigits();
251             }
252             else
253             {
254                 parsedVersionDigits = manifestVersionDigits;
255             }
256             if ( manifestMerger.getUsesSdk() != null )
257             {
258                 parsedUsesSdk = manifestMerger.getUsesSdk();
259             }
260             else
261             {
262                 parsedUsesSdk = manifestUsesSdk;
263             }
264             if ( manifestMerger.getMergeLibraries() != null )
265             {
266                 parsedMergeLibraries = manifestMerger.getMergeLibraries();
267             }
268             else
269             {
270                 parsedMergeLibraries = manifestMergeLibraries;
271             }
272             if ( manifestMerger.getMergeReportFile() != null )
273             {
274                 parsedMergeReportFile = manifestMerger.getMergeReportFile();
275             }
276             else
277             {
278                 parsedMergeReportFile = manifestMergeReportFile;
279             }
280         }
281         else
282         {
283             parsedVersionName = manifestVersionName;
284             parsedVersionCode = manifestVersionCode;
285             parsedUsesSdk = manifestUsesSdk;
286             parsedVersionCodeUpdateFromVersion = manifestVersionCodeUpdateFromVersion;
287             parsedVersionNamingPattern = manifestVersionNamingPattern;
288             parsedVersionDigits = manifestVersionDigits;
289             parsedMergeLibraries = manifestMergeLibraries;
290             parsedMergeReportFile = manifestMergeReportFile;
291         }
292     }
293 
294 
295     public void manifestMergerV2() throws MojoExecutionException, MojoFailureException
296     {
297         ILogger logger = new MavenILogger( getLog(), ( parsedMergeReportFile != null ) );
298         AndroidBuilder builder = new AndroidBuilder( project.toString(), "created by Android Maven Plugin",
299             new DefaultProcessExecutor( logger ),
300             new DefaultJavaProcessExecutor( logger ),
301             new MavenErrorReporter( logger, ErrorReporter.EvaluationMode.STANDARD ),
302             logger,
303           false );
304         
305         String minSdkVersion = null;
306         String targetSdkVersion = null;
307         int versionCode;
308         if ( parsedUsesSdk != null )
309         {
310             minSdkVersion = parsedUsesSdk.getMinSdkVersion();
311             targetSdkVersion = parsedUsesSdk.getTargetSdkVersion();
312         }
313         if ( parsedVersionCodeUpdateFromVersion )
314         {
315             VersionGenerator gen = new VersionGenerator( parsedVersionDigits, parsedVersionNamingPattern );
316 
317             versionCode = gen.generate( parsedVersionName );
318         }
319         else
320         {
321             versionCode = parsedVersionCode;
322         }
323         List<AndroidDependency> manifestDependencies = new ArrayList<>();
324 
325         if ( parsedMergeLibraries )
326         {
327             final Set<Artifact> allArtifacts = project.getDependencyArtifacts();
328             Set<Artifact> dependencyArtifacts = getArtifactResolverHelper().getFilteredArtifacts( allArtifacts );
329 
330             for ( Artifact dependency : dependencyArtifacts )
331             {
332                 final File unpackedLibFolder = getUnpackedLibFolder( dependency );
333                 final File manifestFile = new File( unpackedLibFolder, SdkConstants.FN_ANDROID_MANIFEST_XML );
334                 if ( manifestFile.exists() )
335                 {
336                     /*
337                     File artifactFile,
338                     MavenCoordinates coordinates,
339                      String name,
340                      String projectPath,
341                      File extractedFolder
342                     * */
343                     // TODO this might not be working just yet....
344                     manifestDependencies.add( AndroidDependency.createExplodedAarLibrary(
345                             manifestFile,
346                             null,
347                             dependency.getArtifactId(),
348                             unpackedLibFolder.getPath(),
349                             unpackedLibFolder ) );
350                 }
351             }
352         }
353         /**
354          File mainManifest,
355          List<File> manifestOverlays,
356          List<? extends AndroidLibrary> libraries,
357          String packageOverride,
358          int versionCode,
359          String versionName,
360          String minSdkVersion,
361          String targetSdkVersion,
362          Integer maxSdkVersion,
363          String outManifestLocation,
364          String outAaptSafeManifestLocation,
365          String outInstantRunManifestLocation,
366          MergeType mergeType,
367          Map<String, Object> placeHolders,
368          List<Feature> optionalFeatures,
369          File reportFile)
370          */
371 
372         builder.mergeManifestsForApplication(
373                   androidManifestFile,     // mainManifest
374                   new ArrayList<File>(),   // manifestOverlays
375                   manifestDependencies,    // libraries
376                   "",                      // packageOverride
377                   versionCode,             // versionCode
378                   parsedVersionName,       // versionName
379                   minSdkVersion,           // minSdkVersion
380                   targetSdkVersion,        // targetSdkVersion
381                   null,                    // maxSdkVersion
382                   destinationManifestFile.getPath(),       // outManifestLocation
383                   null,                                    // outAaptSafeManifestLocation
384                   null, // outInstantRunManifestLocation,
385                   ManifestMerger2.MergeType.APPLICATION,   // mergeType
386                   new HashMap<String, Object>(),           // placeHolders
387                   new ArrayList<ManifestMerger2.Invoker.Feature>(),  // optionalFeatures
388                   parsedMergeReportFile                    // reportFile
389                   );
390     }
391 }