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 java.io.File;
23  import java.io.IOException;
24  import java.util.Collection;
25  import java.util.HashSet;
26  import java.util.logging.LogManager;
27  import java.util.logging.Logger;
28  
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugin.MojoFailureException;
31  
32  /**
33   * Appends a suffix to the appId. No actions are taken if the suffix is not specified or the suffix
34   * has zero length.
35   * 
36   * @goal change-app-id
37   * 
38   */
39  public class XCodeChangeAppIDMojo extends BuildContextAwareMojo
40  {
41  
42  	private final static Logger LOGGER = LogManager.getLogManager().getLogger(XCodePluginLogger.getLoggerName());
43  	/**
44  	* This suffix gets appended to the appId as '.&lt;appIdSuffix>' in the <code>Info.plist</code>
45  	* before the signing takes place.
46  	*
47  	* @parameter expression="${xcode.appIdSuffix}"
48  	* @since 1.2.0
49  	*/
50  	private static String appIdSuffix;
51  
52  	/**
53  	* appType helps to inject the appIdSuffix in to appropriate location of appId in the <code>Info.plist</code>
54  	* before the signing takes place.
55  	* Without type appId will be suffixed with the given value, with type we are injecting value at appropriate location
56  	* @parameter expression="${xcode.appType}"
57  	*/
58  	private String appType;
59  
60  	/**
61  	* watchkitAppPlist helps to know the location of watchkitApp plist file
62  	* appIdSuffix will be injected to this plist file
63  	*  @parameter expression="${xcode.watchkitAppPlist}"
64  	*/
65  	private static String watchkitAppPlist;
66  
67  	/**
68  	* watchkitExtensionPlist helps to know the location of watchkitExtension plist file
69  	* appIdSuffix will be injected to this plist file
70  	*  @parameter expression="${xcode.watchkitExtentionPlist}"
71  	*/
72  	private static String watchkitExtentionPlist;
73  
74  	/**
75  	* This is a variable in plist file of Watchkitapp
76  	* This variable reference to the Bundle Identifier of the iphone app
77  	*/
78  	private static String wkCompanionAppBundleIdentifier;
79  
80  
81  	/**
82  	* This is a variable in plist file of watchkitextention
83  	* This variable reference to the Bundle Identifier of the Watchkitapp
84  	*/
85  	private static String wkAppBundleIdentifier;
86  
87  	/**
88  	* This is common variable in all the plist files, we need to make sure values are same in all plist files
89  	*/
90  	private static String cfBundleShortVersionString;
91  
92  	/**
93  	* This is common variable in all the plist files, we need to make sure values are same in all plist files
94  	*/
95  	private static String cfBundleVersion;
96  
97  	/**
98  	* This is a watchkit apps modified bundle identifier value, this needs to be extended for the watchkitextension bundle identifier
99  	* Requirement for the watchos2.0 applications, without with build fails
100 	*/
101 
102 	private static String watchkitAppBundleIdentifier;
103 	/**
104 	* @parameter expression="${xcode.watchapp}"
105 	* For watchos2.0 we should not send the sdk value to xcodebuild, To differentiate between regular app and watch2.0 (Now it's specific)
106 	* expecting this property from developer in pom.xml, on demand this will be considered.
107 	*
108 	* pom.xml entry:
109 	*
110 	* <pre>
111 	* {@code
112 	* <properties>
113 	*  <xcode.watchapp>watchos2.0</xcode.watchapp>
114 	* </properties>
115 	* }
116 	* </pre>
117 	*
118 	* @since 1.14.3
119 	*/
120 	private String watchapp;
121 
122 
123 	@Override
124   public void execute() throws MojoExecutionException, MojoFailureException
125   {
126     if (appIdSuffix == null || "".equals(appIdSuffix.trim())) {
127       return;
128     }
129 
130     LOGGER.info("appIdSuffix=" + appIdSuffix);
131 
132 	final Collection<File> alreadyUpdatedPlists = new HashSet<File>();
133 
134 		for (final String configuration : getConfigurations()) {
135 			for (final String sdk : getSDKs()) {
136 
137 				File infoPlistFile = null;
138 				File srcRoot = null;
139 				PListAccessor infoPlistAccessor = null;
140 
141 				/*
142 				 * Add appIdSuffix to the info.plist of Application
143 				 */
144 				try {
145 					infoPlistFile = getPListFile(XCodeContext.SourceCodeLocation.WORKING_COPY, configuration, sdk);
146 				} catch (XCodeException e) {
147 					throw new MojoExecutionException(e.getMessage(), e);
148 				}
149 
150 				infoPlistAccessor = new PListAccessor(infoPlistFile);
151 				if (alreadyUpdatedPlists.contains(infoPlistFile)) {
152 					LOGGER.finer("PList file '" + infoPlistFile.getName()
153 							+ "' was already updated for another configuration. This file will be skipped.");
154 				} else {
155 					changeAppId(infoPlistAccessor, appIdSuffix, null);
156 					try {
157 						setCFBundleShortVersionString(infoPlistAccessor.getStringValue(PListAccessor.KEY_BUNDLE_SHORT_VERSION_STRING));
158 						setCFBundleVersion(infoPlistAccessor.getStringValue(PListAccessor.KEY_BUNDLE_VERSION));
159 
160 					} catch (IOException e) {
161 						throw new MojoExecutionException(e.getMessage(), e);
162 					}
163 
164 					alreadyUpdatedPlists.add(infoPlistFile);
165 				}
166 
167 				/**
168 				 * If appType provided explicitly; User also needs to specify the additional plist files for appId injection
169 				 * Currently no strict check for the appType which can be extended for specific checks in future.
170 				 * For watchkit Application support: User has to pass parameters as below in pom.xml
171 				 *
172 				 * <pre>
173 				 * {@code
174 				 * <properties>
175 				 *  <xcode.appType>watchKit</xcode.appType>
176 				 *  <xcode.watchkitAppPlist>${watchkitApp-plist-file-path}</xcode.watchkitAppPlist>
177 				 *  <xcode.watchkitExtentionPlist>${watchkitExtention-plist-file-path}</xcode.watchkitExtentionPlist>
178 				 * </properties>
179 				 * }
180 				 * </pre>
181 				 *
182 				 * Sample:
183 				 *
184 				 * Provide the relative paths of the plist files: Project_Root_Dir\{plist-file-path}
185 				 * Ex:
186 				 * AppName: 						"TodoList"
187 				 * WatchKit App plist file: 		"TodoList WatchKit App\Info.plist"
188 				 * WatchKit Extension plist file: 	"TodoList WatchKit Extension\Info.plist"
189 				 * pom.xml entry should look like,
190 				 *
191 				 * <pre>
192 				 * {@code
193 				 * <properties>
194 				 *  <xcode.appType>watchKit</xcode.appType>
195 				 *  <xcode.watchkitAppPlist>TodoList WatchKit App\Info.plist</xcode.watchkitAppPlist>
196 				 *  <xcode.watchkitExtentionPlist>TodoList WatchKit Extension\Info.plist</xcode.watchkitExtentionPlist>
197 				 * </properties>
198 				 * }
199 				 * </pre>
200 				 *
201 				 */
202 				if (!(appType == null || "".equals(appType.trim()))) {
203 					LOGGER.info("appType: " + appType
204 							+ " value provided, needs to consider additional plist files for appIdSuffix injection");
205 
206 					try {
207 						srcRoot = getProjectRootDirectory(XCodeContext.SourceCodeLocation.WORKING_COPY, configuration,
208 								sdk);
209 
210 						if (watchkitAppPlist != null || "".equals(watchkitAppPlist.trim())) {
211 							LOGGER.info("location of watchkitAppPlist: " + watchkitAppPlist);
212 							infoPlistFile = new File(srcRoot, watchkitAppPlist);
213 							infoPlistAccessor = new PListAccessor(infoPlistFile);
214 							if (alreadyUpdatedPlists.contains(infoPlistFile)) {
215 								LOGGER.finer("PList file '" + infoPlistFile.getName()
216 										+ "' was already updated for another configuration. This file will be skipped.");
217 							} else {
218 								try {
219 									changeAppId(infoPlistAccessor, appIdSuffix, appType);
220 
221 									changePlistKeyValue(infoPlistFile,PListAccessor.KEY_WK_COMPANION_APP_BUNDLE_IDENTIFIER,getWKCompanionAppBundleIdentifier());
222 									changePlistKeyValue(infoPlistFile,PListAccessor.KEY_BUNDLE_SHORT_VERSION_STRING,getCFBundleShortVersionString());
223 									changePlistKeyValue(infoPlistFile,PListAccessor.KEY_BUNDLE_VERSION,getCFBundleVersion());
224 								} catch (IOException e) {
225 									throw new MojoExecutionException(e.getMessage(), e);
226 								}
227 							}
228 							setWKAppBundleIdentifier(infoPlistAccessor.getStringValue(PListAccessor.KEY_BUNDLE_IDENTIFIER));
229 							alreadyUpdatedPlists.add(infoPlistFile);
230 						}
231 
232 						if (watchkitExtentionPlist != null || "".equals(watchkitExtentionPlist.trim())) {
233 							LOGGER.info("location of watchkitExtentionPlist: "
234 									+ watchkitExtentionPlist);
235 							infoPlistFile = new File(srcRoot, watchkitExtentionPlist);
236 							infoPlistAccessor = new PListAccessor(infoPlistFile);
237 							if (alreadyUpdatedPlists.contains(infoPlistFile)) {
238 								LOGGER.finer("PList file '" + infoPlistFile.getName()
239 										+ "' was already updated for another configuration. This file will be skipped.");
240 							} else {
241 								changeAppIdForExtension(infoPlistFile, appIdSuffix, appType);
242 
243 								changePlistKeyValue(infoPlistFile,PListAccessor.KEY_WK_APP_BUNDLE_IDENTIFIER,getWKAppBundleIdentifier());
244 								changePlistKeyValue(infoPlistFile,PListAccessor.KEY_BUNDLE_SHORT_VERSION_STRING,getCFBundleShortVersionString());
245 								changePlistKeyValue(infoPlistFile,PListAccessor.KEY_BUNDLE_VERSION,getCFBundleVersion());
246 								alreadyUpdatedPlists.add(infoPlistFile);
247 							}
248 						}
249 
250 					} catch (XCodeException e) {
251 						throw new MojoExecutionException(e.getMessage(), e);
252 					} catch (IOException e) {
253 						throw new MojoExecutionException(e.getMessage(), e);
254 					}
255 				}
256 			}
257 		}
258 	}
259 
260 	static void changeAppIdForExtension(File plist, String appIdSuffix, String appType) throws MojoExecutionException {
261 		PListAccessor infoPlistAccessor = new PListAccessor(plist);
262 		ensurePListFileIsWritable(infoPlistAccessor.getPlistFile());
263 		try {
264 			LOGGER.info("Watchkit extension bundle ID is combination of WatchkitApp BI and extension string");
265 			LOGGER.info("WatchApp Bundle Identifier Value: "+getWKAppBundleIdentifier());
266 			//String watchkitExtensionBI= getWKAppBundleIdentifier()+"."+appIdSuffix+".watchkitextension";
267 			String watchkitExtensionBI= getWKAppBundleIdentifier()+".watchkitextension";
268 			LOGGER.info("So watchkitExtention BI: "+watchkitExtensionBI);
269 			infoPlistAccessor.updateStringValue(PListAccessor.KEY_BUNDLE_IDENTIFIER, watchkitExtensionBI);
270 		}
271 		catch (IOException e) {
272 			throw new MojoExecutionException(e.getMessage(), e);
273 		}
274 	}
275 
276 	static void changeAppId(PListAccessor infoPlistAccessor, String appIdSuffix, String appType) throws MojoExecutionException{
277 		ensurePListFileIsWritable(infoPlistAccessor.getPlistFile());
278 		try {
279 			appendAppIdSuffix(infoPlistAccessor, appIdSuffix, appType);
280 		}
281 		catch (IOException e) {
282 			throw new MojoExecutionException(e.getMessage(), e);
283 		}
284 	}
285 
286   private static void ensurePListFileIsWritable(File pListFile) throws MojoExecutionException
287 	{
288     if (!pListFile.canWrite()) {
289     	if (!pListFile.setWritable(true, true))
290     	throw new MojoExecutionException("Could not make plist file '" + pListFile + "' writable.");
291 		LOGGER.info("Made PList file '" + pListFile + "' writable.");
292 	}
293 	}
294 
295   private static void appendAppIdSuffix(PListAccessor infoPlistAccessor, String appIdSuffix, String appType) throws IOException
296 	{
297 	String newAppId;
298 	if (appType == null || "".equals(appType.trim())) {
299 		String originalAppId = infoPlistAccessor.getStringValue(PListAccessor.KEY_BUNDLE_IDENTIFIER);
300 		newAppId = originalAppId + "." + appIdSuffix;
301 		LOGGER.info("Original AppId value : "+ originalAppId);
302 		LOGGER.info("New upated AppID value: "+ newAppId);
303 		setWKCompanionAppBundleIdentifier(newAppId);
304 
305 		String CFBundleName = infoPlistAccessor.getStringValue("CFBundleName");
306 		LOGGER.info("CFBundleName : "+ CFBundleName);
307 	}else{
308 			newAppId = injectAppIdSuffix(appIdSuffix, infoPlistAccessor);
309 		}
310 
311 	infoPlistAccessor.updateStringValue(PListAccessor.KEY_BUNDLE_IDENTIFIER, newAppId);
312 	LOGGER.info("PList file '" + infoPlistAccessor.getPlistFile() + "' updated: Set AppId to '" + newAppId + "'.");
313 	}
314 
315 
316   private static String injectAppIdSuffix(String appIdSuffix, PListAccessor infoPlistAccessor) throws IOException {
317 	String originalAppId = infoPlistAccessor.getStringValue(PListAccessor.KEY_BUNDLE_IDENTIFIER);
318 	String firstPart= originalAppId.substring(0,originalAppId.lastIndexOf("."));
319 	String lastPart = originalAppId.substring(originalAppId.lastIndexOf(".")+ 1);
320 	String newAppId = firstPart + "." + appIdSuffix + "." + lastPart;
321 	LOGGER.info("Original AppId value : "+ originalAppId);
322 	LOGGER.info("New updated AppID value: "+ newAppId);
323 	return newAppId;
324 	}
325 
326   private static void changePlistKeyValue(File plist, String key, String value) throws MojoExecutionException, IOException{
327 	  PListAccessor infoPlistAccessor = new PListAccessor(plist);
328 	  ensurePListFileIsWritable(infoPlistAccessor.getPlistFile());
329 	  infoPlistAccessor.updateStringValue(key, value);
330 	  LOGGER.info("PList file '" + plist.getAbsolutePath() + "' updated: Set "
331 			  + "Key " + key  + "and Value " + value);
332     }
333 
334 	public static String getWKCompanionAppBundleIdentifier() {
335 		return wkCompanionAppBundleIdentifier;
336 	}
337 
338 	public static void setWKCompanionAppBundleIdentifier(String wKCompanionAppBundleIdentifier) {
339 		wkCompanionAppBundleIdentifier = wKCompanionAppBundleIdentifier;
340 	}
341 
342 	public static String getWKAppBundleIdentifier() {
343 		return wkAppBundleIdentifier;
344 	}
345 
346 	public static void setWKAppBundleIdentifier(String wKAppBundleIdentifier) {
347 		wkAppBundleIdentifier = wKAppBundleIdentifier;
348 	}
349 
350 	public static String getCFBundleShortVersionString() {
351 		return cfBundleShortVersionString;
352 	}
353 
354 	public static void setCFBundleShortVersionString(String cFBundleShortVersionString) {
355 		cfBundleShortVersionString = cFBundleShortVersionString;
356 	}
357 
358 	public static String getCFBundleVersion() {
359 		return cfBundleVersion;
360 	}
361 
362 	public static void setCFBundleVersion(String cFBundleVersion) {
363 		cfBundleVersion = cFBundleVersion;
364 	}
365 
366 	 public static String getWatchkitAppBundleIdentifier() {
367 			return watchkitAppBundleIdentifier;
368 	}
369 
370 	public static void setWatchkitAppBundleIdentifier(String watchkitAppBundleIdentifier) {
371 			XCodeChangeAppIDMojo.watchkitAppBundleIdentifier = watchkitAppBundleIdentifier;
372 	}
373 
374 
375 }