/* * ### * Xcodebuild Command-Line Wrapper * * Copyright (C) 1999 - 2012 Photon Infotech Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ### */ /******************************************************************************* * Copyright (c) 2012 Photon infotech. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Photon Public License v1.0 * which accompanies this distribution, and is available at * http://www.photon.in/legal/ppl-v10.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. * * Contributors: * Photon infotech - initial API and implementation ******************************************************************************/ package com.photon.phresco.plugins.xcode; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import com.photon.phresco.plugin.commons.PluginUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.codehaus.jettison.json.JSONObject; import org.codehaus.plexus.archiver.zip.ZipArchiver; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.photon.phresco.commons.BuildInfo; import com.photon.phresco.commons.XCodeConstants; import com.photon.phresco.exception.PhrescoException; import com.photon.phresco.plugins.xcode.utils.SdkVerifier; import com.photon.phresco.plugins.xcode.utils.XcodeUtil; /** * Run the xcodebuild command line program * * @goal xcodebuild * @phase compile */ public class XcodeBuild extends AbstractMojo { private static final String DO_NOT_CHECKIN_BUILD = "/do_not_checkin/build"; /** * Location of the xcodebuild executable. * * @parameter expression="${xcodebuild}" default-value="/usr/bin/xcodebuild" */ private File xcodeCommandLine; /** * Project Name * * @parameter */ private String xcodeProject; /** * Target to be built * * @parameter expression="${targetName}" */ private String xcodeTarget; /** * @parameter expression="${encrypt}" */ private boolean encrypt; /** * The maven project. * * @parameter expression="${project}" * @required * @readonly */ protected MavenProject project; /** * @parameter expression="${basedir}" */ private String basedir; /** * @parameter expression="${unittest}" */ private boolean unittest; /** * Build directory. * * @parameter expression="${project.build.directory}" * @required */ private File buildDirectory; /** * @parameter expression="${configuration}" default-value="Debug" */ private String configuration; /** * @parameter expression="${sdk}" default-value="iphonesimulator5.0" */ private String sdk; /** * @parameter */ protected String gccpreprocessor; /** * The java sources directory. * * @parameter default-value="${project.basedir}" * * @readonly */ protected File baseDir; /** * @parameter expression="${environmentName}" required="true" */ protected String environmentName; /** * XML property list file. In this file the webserverName * * @parameter expression="${plistfile}" * default-value="phresco-env-config.xml" */ protected String plistFile; /** * @parameter expression="${buildNumber}" required="true" */ protected String buildNumber; protected int buildNo; private File srcDir; private File buildDirFile; private File buildInfoFile; private List<BuildInfo> buildInfoList; private int nextBuildNo; private Date currentDate; private String appFileName; private String dSYMFileName; private String deliverable; private Map<String, Object> sdkOptions; /** * Execute the xcode command line utility. */ public void execute() throws MojoExecutionException { if (!xcodeCommandLine.exists()) { throw new MojoExecutionException("Invalid path, invalid xcodebuild file: " + xcodeCommandLine.getAbsolutePath()); } /* * // Compute archive name String archiveName = * project.getBuild().getFinalName() + ".cust"; File finalDir = new * File(buildDirectory, archiveName); * * // Configure archiver MavenArchiver archiver = new MavenArchiver(); * archiver.setArchiver(jarArchiver); archiver.setOutputFile(finalDir); */ try { if(!SdkVerifier.isAvailable(sdk)) { throw new MojoExecutionException("Selected version " +sdk +" is not available!"); } } catch (IOException e2) { throw new MojoExecutionException("SDK verification failed!"); } catch (InterruptedException e2) { throw new MojoExecutionException("SDK verification interrupted!"); } try { init(); configure(); ProcessBuilder pb = new ProcessBuilder(xcodeCommandLine.getAbsolutePath()); // Include errors in output pb.redirectErrorStream(true); List<String> commands = pb.command(); if (xcodeProject != null) { commands.add("-project"); commands.add(xcodeProject); } if (StringUtils.isNotBlank(configuration)) { commands.add("-configuration"); commands.add(configuration); } if (StringUtils.isNotBlank(sdk)) { commands.add("-sdk"); commands.add(sdk); } commands.add("OBJROOT=" + buildDirectory); commands.add("SYMROOT=" + buildDirectory); commands.add("DSTROOT=" + buildDirectory); if (StringUtils.isNotBlank(xcodeTarget)) { commands.add("-target"); commands.add(xcodeTarget); } if(StringUtils.isNotBlank(gccpreprocessor)) { commands.add("GCC_PREPROCESSOR_DEFINITIONS="+gccpreprocessor); } if (unittest) { commands.add("clean"); commands.add("build"); } getLog().info("List of commands" + pb.command()); // pb.command().add("install"); pb.directory(new File(basedir)); Process child = pb.start(); // Consume subprocess output and write to stdout for debugging InputStream is = new BufferedInputStream(child.getInputStream()); int singleByte = 0; while ((singleByte = is.read()) != -1) { // output.write(buffer, 0, bytesRead); System.out.write(singleByte); } child.waitFor(); int exitValue = child.exitValue(); getLog().info("Exit Value: " + exitValue); if (exitValue != 0) { throw new MojoExecutionException("Compilation error occured. Resolve the error(s) and try again!"); } if(!unittest) { //In case of unit testcases run, the APP files will not be generated. createdSYM(); createApp(); } /* * child.waitFor(); * * InputStream in = child.getInputStream(); InputStream err = * child.getErrorStream(); getLog().error(sb.toString()); */ } catch (IOException e) { getLog().error("An IOException occured."); throw new MojoExecutionException("An IOException occured", e); } catch (InterruptedException e) { getLog().error("The clean process was been interrupted."); throw new MojoExecutionException("The clean process was been interrupted", e); } catch (MojoFailureException e) { // TODO Auto-generated catch block e.printStackTrace(); } File directory = new File(this.basedir + "/pom.xml"); this.project.getArtifact().setFile(directory); } private void init() throws MojoExecutionException, MojoFailureException { try { // To Delete the buildDirectory if already exists if (buildDirectory.exists()) { FileUtils.deleteDirectory(buildDirectory); buildDirectory.mkdirs(); } buildInfoList = new ArrayList<BuildInfo>(); // initialization // srcDir = new File(baseDir.getPath() + File.separator + // sourceDirectory); buildDirFile = new File(baseDir, DO_NOT_CHECKIN_BUILD); if (!buildDirFile.exists()) { buildDirFile.mkdirs(); getLog().info("Build directory created..." + buildDirFile.getPath()); } buildInfoFile = new File(buildDirFile.getPath() + "/build.info"); System.out.println("file created " + buildInfoFile); nextBuildNo = generateNextBuildNo(); currentDate = Calendar.getInstance().getTime(); } catch (IOException e) { throw new MojoFailureException("APP could not initialize " + e.getLocalizedMessage()); } } private int generateNextBuildNo() throws IOException { int nextBuildNo = 1; if (!buildInfoFile.exists()) { return nextBuildNo; } BufferedReader read = new BufferedReader(new FileReader(buildInfoFile)); String content = read.readLine(); Gson gson = new Gson(); java.lang.reflect.Type listType = new TypeToken<List<BuildInfo>>() { }.getType(); buildInfoList = (List<BuildInfo>) gson.fromJson(content, listType); if (buildInfoList == null || buildInfoList.size() == 0) { return nextBuildNo; } int buildArray[] = new int[buildInfoList.size()]; int count = 0; for (BuildInfo buildInfo : buildInfoList) { buildArray[count] = buildInfo.getBuildNo(); count++; } Arrays.sort(buildArray); // sort to the array to find the max build no nextBuildNo = buildArray[buildArray.length - 1] + 1; // increment 1 to // the max in // the build // list return nextBuildNo; } private void createApp() throws MojoExecutionException { File outputFile = getAppName(); if (outputFile == null) { getLog().error("xcodebuild failed. resultant APP not generated!"); throw new MojoExecutionException("xcodebuild has been failed"); } if (outputFile.exists()) { try { System.out.println("Completed " + outputFile.getAbsolutePath()); getLog().info("APP created.. Copying to Build directory....."); String buildName = project.getBuild().getFinalName() + '_' + getTimeStampForBuildName(currentDate); File baseFolder = new File(baseDir + DO_NOT_CHECKIN_BUILD, buildName); if (!baseFolder.exists()) { baseFolder.mkdirs(); getLog().info("build output direcory created at " + baseFolder.getAbsolutePath()); } File destFile = new File(baseFolder, outputFile.getName()); getLog().info("Destination file " + destFile.getAbsolutePath()); XcodeUtil.copyFolder(outputFile, destFile); getLog().info("copied to..." + destFile.getName()); appFileName = destFile.getAbsolutePath(); getLog().info("Creating deliverables....."); ZipArchiver zipArchiver = new ZipArchiver(); zipArchiver.addDirectory(baseFolder); File deliverableZip = new File(baseDir + DO_NOT_CHECKIN_BUILD, buildName + ".zip"); zipArchiver.setDestFile(deliverableZip); zipArchiver.createArchive(); deliverable = deliverableZip.getAbsolutePath(); getLog().info("Deliverables available at " + deliverableZip.getName()); writeBuildInfo(true); } catch (IOException e) { throw new MojoExecutionException("Error in writing output..." + e.getLocalizedMessage()); } } else { getLog().info("output directory not found"); } } private void createdSYM() throws MojoExecutionException { File outputFile = getdSYMName(); if (outputFile == null) { getLog().error("xcodebuild failed. resultant dSYM not generated!"); throw new MojoExecutionException("xcodebuild has been failed"); } if (outputFile.exists()) { try { System.out.println("Completed " + outputFile.getAbsolutePath()); getLog().info("dSYM created.. Copying to Build directory....."); String buildName = project.getBuild().getFinalName() + '_' + getTimeStampForBuildName(currentDate); File baseFolder = new File(baseDir + DO_NOT_CHECKIN_BUILD, buildName); if (!baseFolder.exists()) { baseFolder.mkdirs(); getLog().info("build output direcory created at " + baseFolder.getAbsolutePath()); } File destFile = new File(baseFolder, outputFile.getName()); getLog().info("Destination file " + destFile.getAbsolutePath()); XcodeUtil.copyFolder(outputFile, destFile); getLog().info("copied to..." + destFile.getName()); dSYMFileName = destFile.getAbsolutePath(); getLog().info("Creating deliverables....."); ZipArchiver zipArchiver = new ZipArchiver(); zipArchiver.addDirectory(baseFolder); File deliverableZip = new File(baseDir + DO_NOT_CHECKIN_BUILD, buildName + ".zip"); zipArchiver.setDestFile(deliverableZip); zipArchiver.createArchive(); deliverable = deliverableZip.getAbsolutePath(); getLog().info("Deliverables available at " + deliverableZip.getName()); } catch (IOException e) { throw new MojoExecutionException("Error in writing output..." + e.getLocalizedMessage()); } } else { getLog().info("output directory not found"); } } private File getAppName() { String path = configuration + "-"; if (sdk.startsWith("iphoneos")) { path = path + "iphoneos"; } else { path = path + "iphonesimulator"; } File baseFolder = new File(buildDirectory, path); File[] files = baseFolder.listFiles(); for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.getName().endsWith("app")) { return file; } } return null; } private File getdSYMName() { String path = configuration + "-"; if (sdk.startsWith("iphoneos")) { path = path + "iphoneos"; } else { path = path + "iphonesimulator"; } File baseFolder = new File(buildDirectory, path); File[] files = baseFolder.listFiles(); for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.getName().endsWith("dSYM")) { return file; } } return null; } private void writeBuildInfo(boolean isBuildSuccess) throws MojoExecutionException { try { if (buildNumber != null) { buildNo = Integer.parseInt(buildNumber); } PluginUtils pu = new PluginUtils(); BuildInfo buildInfo = new BuildInfo(); List<String> envList = pu.csvToList(environmentName); if (buildNo > 0) { buildInfo.setBuildNo(buildNo); } else { buildInfo.setBuildNo(nextBuildNo); } buildInfo.setTimeStamp(getTimeStampForDisplay(currentDate)); if (isBuildSuccess) { buildInfo.setBuildStatus("SUCCESS"); } else { buildInfo.setBuildStatus("FAILURE"); } buildInfo.setBuildName(appFileName); buildInfo.setDeliverables(deliverable); buildInfo.setEnvironments(envList); Map<String, Boolean> sdkOptions = new HashMap<String, Boolean>(2); boolean isDeviceBuild = Boolean.FALSE; if (sdk.startsWith("iphoneos")) { isDeviceBuild = Boolean.TRUE; } sdkOptions.put(XCodeConstants.CAN_CREATE_IPA, isDeviceBuild); sdkOptions.put(XCodeConstants.DEVICE_DEPLOY, isDeviceBuild); buildInfo.setOptions(sdkOptions); // Gson gson2 = new Gson(); // String json = gson2.toJson(sdkOptions); // System.out.println("json = " + json); buildInfoList.add(buildInfo); Gson gson2 = new Gson(); FileWriter writer = new FileWriter(buildInfoFile); //gson.toJson(buildInfoList, writer); String json = gson2.toJson(buildInfoList); System.out.println("json = " + json); writer.write(json); writer.close(); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } } private String getTimeStampForDisplay(Date currentDate) { SimpleDateFormat formatter = new SimpleDateFormat("dd/MMM/yyyy HH:mm:ss"); String timeStamp = formatter.format(currentDate.getTime()); return timeStamp; } private String getTimeStampForBuildName(Date currentDate) { SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy-HH-mm-ss"); String timeStamp = formatter.format(currentDate.getTime()); return timeStamp; } private void configure() throws MojoExecutionException { if (StringUtils.isEmpty(environmentName)) { return; } try { getLog().info("Configuring the project...."); getLog().info("environment name :" + environmentName); getLog().info("base dir name :" + baseDir.getName()); File srcConfigFile = new File(baseDir, project.getBuild().getSourceDirectory() + File.separator + plistFile); String basedir = baseDir.getName(); PluginUtils pu = new PluginUtils(); pu.executeUtil(environmentName, basedir, srcConfigFile); pu.setDefaultEnvironment(environmentName, srcConfigFile); // if(encrypt) { // pu.encode(srcConfigFile); // } } catch (PhrescoException e) { throw new MojoExecutionException(e.getMessage(), e); } } }