View Javadoc

1   /*
2    * #%L
3    * xcode-maven-plugin
4    * %%
5    * Copyright (C) 2012 SAP AG
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.sap.prd.mobile.ios.mios;
21  
22  import static com.sap.prd.mobile.ios.mios.XCodeVersionUtil.checkVersions;
23  import static com.sap.prd.mobile.ios.mios.XCodeVersionUtil.getVersion;
24  import static com.sap.prd.mobile.ios.mios.XCodeVersionUtil.getXCodeVersionString;
25  
26  import java.io.File;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
36  import org.apache.maven.execution.MavenSession;
37  import org.apache.maven.plugin.MojoExecutionException;
38  
39  /**
40   * Contains all parameters and methods that are needed for mojos that invoke the 'xcodebuild'
41   * command.
42   * 
43   */
44  public abstract class BuildContextAwareMojo extends AbstractXCodeMojo
45  {
46  
47    private static final String PREFIX_XCODE_OPTIONS = "xcode.options.";
48    private static final String PREFIX_XCODE_SETTINGS = "xcode.settings.";
49    public final static String MIN_XCODE_VERSION = "8.0";
50  
51  
52    protected final static List<String> DEFAULT_BUILD_ACTIONS = Collections.unmodifiableList(Arrays.asList("clean",
53          "build"));
54  
55    /**
56     * The Xcode build action to to execute (e.g. clean, build, install). By default
57     * <code>clean</code> and <code>build</code> are executed.
58     * 
59     * @parameter
60     */
61    protected List<String> buildActions;
62  
63    /**
64     * The code sign identity is used to select the provisioning profile (e.g.
65     * <code>iPhone Distribution</code>, <code>iPhone Developer</code>).
66     * 
67     * @parameter expression="${xcode.codeSignIdentity}"
68     * @since 1.2.0
69     */
70    protected String codeSignIdentity;
71  
72    /**
73     * The code signing required is used to disable code signing when no
74     * developer provisioning certificate is available (e.g.
75     * <code>NO</code>, <code>YES</code>).
76     * 
77     * @parameter expression="${xcode.codeSigningRequired}" default-value = "true"
78     * @since 1.14.1
79     */
80    protected boolean codeSigningRequired;
81    
82    /**
83     * Can be used to override the provisioning profile defined in the Xcode project target. You can
84     * set it to an empty String if you want to use the default provisioning profile.
85     * 
86     * @parameter expression="${xcode.provisioningProfile}"
87     * @since 1.2.1
88     */
89    protected String provisioningProfile;
90  
91    /**
92     * The Xcode target to be built. If not specified, the default target (the first target) will be
93     * built.
94     * 
95     * @parameter expression="${xcode.target}"
96     * @since 1.4.1
97     */
98    protected String target;
99  
100   /**
101    * @parameter expression="${product.name}"
102    */
103   private String productName;
104 
105   /**
106    * Settings to pass to XCode - if any are explicitly defined here, this plugin will not provide
107    * default settings to XCode.
108    * 
109    * @parameter
110    * @since 1.6.2
111    */
112   private Map<String, String> settings;
113 
114   /**
115    * Options to pass to XCode - if any are explicitly defined here, this plugin will not provide
116    * default options to XCode.
117    * 
118    * @parameter
119    * @since 1.6.2
120    */
121   private Map<String, String> options;
122 
123   /**
124    * @parameter expression="${session}"
125    * @required
126    * @readonly
127    */
128   private MavenSession session;
129 
130   /**
131    * @parameter expression="${xcode.watchapp}"
132    * For watchos2.0 we should not send the sdk value to xcodebuild, To differentiate between regular app and watch2.0 (Now it's specific)
133    * expecting this property from developer in pom.xml, on demand this will be considered.
134    *
135    * pom.xml entry:
136    *
137    * <pre>
138    * {@code
139    * <properties>
140    *  <xcode.watchapp>watchos2.0</xcode.watchapp>
141    * </properties>
142    * }
143    * </pre>
144    *
145    * @since 1.14.3
146    */
147   private String watchapp;
148 
149   /**
150    * Allowed developers to override the SYMROOT settings
151    *
152    * @parameter expression="${xcode.symroot}" default-value = "build"
153    * @since 1.14.4
154    */
155   private String symRootDir;
156   /**
157    * Allowed developers to override the xcconfig settings through pom.xml
158    * Signing methodology has been changed with xcode8 onwards, to enable this we are expecting dev to give singing related parameters
159    * This is only for the Apps with Entitlements like Push notification, Wallet etc...
160    * @parameter expression="${xcode.xcconfig}"
161    *
162    * pom.xml entry:
163    *
164    * <pre>
165    * {@code
166    * <properties>
167    *  <xcode.xcconfig>$XCCONFG_FILE_PATH</xcode.xcconfig>
168    * </properties>
169    * }
170    * </pre>
171    * where XCCONFG_FILE_PATH = Relative path to xcconfig file, ideally we expect developer to keep in xcode project level
172    *
173    * @since 1.14.5
174    */
175   private String xcconfigDir;
176 
177   /**
178    * For simple application central team manages the xcconfig.
179    * This will be managed in settings.xml
180    *
181    * @parameter expression="${xcode.xcconfig.default}"
182    * @since 1.14.5
183    */
184   private String defaultxcconfig;
185 
186   protected XCodeContext getXCodeContext(final XCodeContext.SourceCodeLocation sourceCodeLocation,
187         String configuration, String sdk)
188   {
189     final String projectName = project.getArtifactId();
190     File projectDirectory = null;
191 
192     if (sourceCodeLocation == XCodeContext.SourceCodeLocation.WORKING_COPY) {
193       projectDirectory = getXCodeCompileDirectory();
194     }
195     else if (sourceCodeLocation == XCodeContext.SourceCodeLocation.ORIGINAL) {
196       projectDirectory = getXCodeSourceDirectory();
197     }
198     else {
199       throw new IllegalStateException("Invalid source code location: '" + sourceCodeLocation + "'");
200     }
201 
202     HashMap<String, String> managedSettings = new HashMap<String, String>();
203     if (codeSignIdentity != null)
204       managedSettings.put(Settings.ManagedSetting.CODE_SIGN_IDENTITY.name(), codeSignIdentity);
205 
206     if (symRootDir != null)
207         managedSettings.put(Settings.ManagedSetting.SYMROOT.name(), symRootDir);
208 
209     if (!codeSigningRequired)
210       managedSettings.put(Settings.ManagedSetting.CODE_SIGNING_REQUIRED.name(), "NO");
211 
212     if (provisioningProfile != null)
213       managedSettings.put(Settings.ManagedSetting.PROVISIONING_PROFILE.name(), provisioningProfile);
214 
215     HashMap<String, String> managedOptions = new HashMap<String, String>();
216 
217     if (configuration != null && !configuration.trim().isEmpty())
218       managedOptions.put(Options.ManagedOption.CONFIGURATION.getOptionName(), configuration);
219 		try {
220 
221 			String xCodeVersionString = getXCodeVersionString();
222 			DefaultArtifactVersion version = getVersion(xCodeVersionString);
223 			File file;
224 			if (checkVersions(version, MIN_XCODE_VERSION)) {
225 
226 				if (xcconfigDir != null) {
227 					getLog().info("Using xccconfig provided by the dev team: " + xcconfigDir);
228 
229 					file = new File(xcconfigDir);
230 					if (file.exists()) {
231 						managedOptions.put(Options.ManagedOption.XCCONFIG.getOptionName(), xcconfigDir);
232 					} else {
233 						getLog().error("xcconfig file not found in locaion " + xcconfigDir);
234 					}
235 				} else if (defaultxcconfig != null) {
236 					getLog().info("Using xccconfig provided by the central team: " + defaultxcconfig);
237 
238 					file = new File(defaultxcconfig);
239 					if (file.exists()) {
240 						managedOptions.put(Options.ManagedOption.XCCONFIG.getOptionName(), defaultxcconfig);
241 					} else {
242 						getLog().error("xcconfig file not found in locaion " + defaultxcconfig);
243 					}
244 				} else
245 					getLog().info(
246 							"To build the application using Xcode 8 and above, plugin expects xcconfig file /n For simple app: Central team manages it in settings.xml "
247 									+ "For apps with entitlement dev needs to provide the xcconfig content in pom.xml");
248 			}
249 		} catch (XCodeException e) {
250 			throw new IllegalStateException("Could not get xcodebuild version", e);
251 		}
252 
253     /**
254      * No specific check has been done here, If property specified then sdk entry will be ignored
255      * This can be extended with the specific check. I kept this as Generic because watchosX.X should support with this check,
256      * without confusing developers it will serve the purpose
257      */
258     if (sdk != null && !sdk.trim().isEmpty() && watchapp ==null)
259       managedOptions.put(Options.ManagedOption.SDK.getOptionName(), sdk);
260     if (target != null && !target.trim().isEmpty())
261       managedOptions.put(Options.ManagedOption.TARGET.getOptionName(), target);
262 
263     Map<String, String> _settings = new HashMap<String, String>(settings == null ? new HashMap<String, String>()
264           : settings);
265 
266     for (String key : getKeys(PREFIX_XCODE_SETTINGS)) {
267       _settings.put(key.substring(PREFIX_XCODE_SETTINGS.length()), getProperty(key));
268     }
269 
270     Map<String, String> _options = new HashMap<String, String>(options == null ? new HashMap<String, String>()
271           : options);
272 
273     for (String key : getKeys(PREFIX_XCODE_OPTIONS)) {
274       _options.put(key.substring(PREFIX_XCODE_OPTIONS.length()), getProperty(key));
275     }
276 
277     if (null == _options.get("scheme"))
278     managedOptions.put(Options.ManagedOption.PROJECT.getOptionName(), projectName + ".xcodeproj");
279 
280 
281     return new XCodeContext(getBuildActions(), projectDirectory, System.out, new Settings(_settings, managedSettings),
282           new Options(_options, managedOptions));
283   }
284 
285   protected List<String> getBuildActions()
286   {
287     return (buildActions == null || buildActions.isEmpty()) ? DEFAULT_BUILD_ACTIONS : Collections
288       .unmodifiableList(buildActions);
289   }
290 
291   /**
292    * Retrieves the Info Plist out of the effective Xcode project settings and returns the accessor
293    * to it.
294    */
295   protected PListAccessor getInfoPListAccessor(XCodeContext.SourceCodeLocation location, String configuration,
296         String sdk)
297         throws MojoExecutionException, XCodeException
298   {
299     File plistFile = getPListFile(location, configuration, sdk);
300     if (!plistFile.isFile()) {
301       throw new MojoExecutionException("The Xcode project refers to the Info.plist file '" + plistFile
302             + "' that does not exist.");
303     }
304     return new PListAccessor(plistFile);
305   }
306 
307   protected File getPListFile(XCodeContext.SourceCodeLocation location, String configuration, String sdk)
308         throws XCodeException
309   {
310 
311     XCodeContext context = getXCodeContext(location, configuration, sdk);
312 
313     String plistFileName = EffectiveBuildSettings.getBuildSetting(context, EffectiveBuildSettings.INFOPLIST_FILE);
314     File srcRoot = new File(EffectiveBuildSettings.getBuildSetting(context, EffectiveBuildSettings.SRC_ROOT));
315 
316     final File plistFile = new File(plistFileName);
317 
318     if (!plistFile.isAbsolute()) {
319       return new File(srcRoot, plistFileName);
320     }
321 
322     if (FileUtils.isChild(srcRoot, plistFile))
323       return plistFile;
324 
325     throw new IllegalStateException("Plist file " + plistFile + " is not located inside the xcode project " + srcRoot
326           + ".");
327 
328   }
329 
330   protected File getProjectRootDirectory(XCodeContext.SourceCodeLocation location, String configuration, String sdk)
331 		throws XCodeException {
332 	XCodeContext context = getXCodeContext(location, configuration, sdk);
333 
334 	File srcRoot = new File(EffectiveBuildSettings.getBuildSetting(context, EffectiveBuildSettings.SRC_ROOT));
335 	return srcRoot;
336   }
337 
338   protected String getProductName(final String configuration, final String sdk) throws MojoExecutionException
339   {
340 
341     final String productName;
342 
343     if (this.productName != null) {
344       productName = this.productName;
345       getLog().info("Production name obtained from pom file");
346     }
347     else {
348 
349       try {
350         productName = EffectiveBuildSettings.getBuildSetting(
351               getXCodeContext(XCodeContext.SourceCodeLocation.WORKING_COPY, configuration, sdk), EffectiveBuildSettings.PRODUCT_NAME);
352         getLog().info("Product name obtained from effective build settings file");
353 
354       }
355       catch (final XCodeException ex) {
356         throw new MojoExecutionException("Cannot get product name: " + ex.getMessage(), ex);
357       }
358     }
359 
360     if (productName == null || productName.trim().length() == 0)
361       throw new MojoExecutionException("Invalid product name. Was null or empty.");
362 
363     return productName;
364   }
365 
366   /**
367    * Returns all keys of project properties and user properties matching the <code>prefix</code>.
368    * 
369    * @param prefix
370    *          all keys if null
371    * @return
372    */
373   @SuppressWarnings("unchecked")
374   protected Set<String> getKeys(String prefix)
375   {
376 
377     Set<String> result = new HashSet<String>();
378 
379     @SuppressWarnings("rawtypes")
380     final Set keys = new HashSet();
381     keys.addAll(session.getUserProperties().keySet());
382     keys.addAll(project.getProperties().keySet());
383 
384     if (prefix == null) return keys;
385 
386     for (Object key : keys) {
387       if (((String) key).startsWith(prefix))
388         result.add((String) key);
389     }
390 
391     return result;
392   }
393 
394   protected String getProperty(String key)
395   {
396     String value = session.getUserProperties().getProperty(key);
397 
398     if (value == null)
399     {
400       value = project.getProperties().getProperty(key);
401     }
402 
403     return value;
404   }
405 
406 }