Java tutorial
/* * #%L * xcode-maven-plugin * %% * Copyright (C) 2012 SAP AG * %% * 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. * #L% */ package com.sap.prd.mobile.ios.mios; import static com.sap.prd.mobile.ios.mios.XCodeVersionUtil.checkVersions; import static com.sap.prd.mobile.ios.mios.XCodeVersionUtil.getVersion; import static com.sap.prd.mobile.ios.mios.XCodeVersionUtil.getXCodeVersionString; import static java.lang.String.format; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.xml.bind.JAXBException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProjectHelper; import org.sonatype.aether.RepositorySystem; import org.sonatype.aether.RepositorySystemSession; import org.sonatype.aether.repository.RemoteRepository; import org.xml.sax.SAXException; import com.sap.prd.mobile.ios.mios.CodeSignManager.ExecResult; import com.sap.prd.mobile.ios.mios.CodeSignManager.ExecutionResultVerificationException; import com.sap.prd.mobile.ios.mios.versioninfo.v_1_2_2.Dependency; /** * Generates a <artifact-id>-<version>-version.xml for reproducibility reasons. This * versions.xml contains information about the scm location and revision of the built project and * all its dependencies. Expects a sync.info file in the root folder of the project as input. * * * The sync.info file is a property file. If used with perforce it must contain the following entries: * <code> * <ul> * <li> type=perforce * <li> port=<The url of the perforce server> * <li> depotpath=<The path synced on the perforce server> * <li> changelist=<The changelist of the change that is being built> * </ul> * </code> * * * If used with git it must contain the following entries: * * <code> * <ul> * <li> type=git * <li> repo=<The git repository> * <li> commitId=<The commitId of the change that is being built> * </ul> * </code> * * For git based projects the sync.info file can be created with the following code snipped executed before the xcode-maven-plugin is triggered. * * <pre> * echo "type=git" > sync.info * echo "repo=scm:git:$(git remote -v |awk '/fetch/ {print $2;}')" >> sync.info * echo "commitId=$(git rev-parse HEAD)" >> sync.info * </pre> * * @goal attach-version-info * @requiresDependencyResolution */ public class XCodeVersionInfoMojo extends BuildContextAwareMojo { private final static String MIN_XCODE_VERSION_NO_STRICT_VERIFY = "6.0.0"; private final static boolean DEFAULT_NO_STRICT_VERIFY_FOR_OLD_XCODE = false; private final static boolean DEFAULT_NO_STRICT_VERIFY_FOR_NEW_XCODE = true; /** * The code sign identity is used to select the provisioning profile (e.g. * <code>iPhone Distribution</code>, <code>iPhone Developer</code>). * * @parameter expression="${xcode.codeSignIdentity}" * @since 1.2.0 */ protected String codeSignIdentity; /** * The code signing required is used to disable code signing when no * developer provisioning certificate is available (e.g. * <code>NO</code>, <code>YES</code>). * * @parameter expression="${xcode.codeSigningRequired}" default-value = "true" * @since 1.14.1 */ protected boolean codeSigningRequired; /** * * @parameter default-value="${session}" * @required * @readonly */ protected MavenSession mavenSession; /** * The entry point to Aether, i.e. the component doing all the work. * * @component */ protected RepositorySystem repoSystem; /** * The current repository/network configuration of Maven. * * @parameter default-value="${repositorySystemSession}" * @readonly */ protected RepositorySystemSession repoSession; /** * The project's remote repositories to use for the resolution of project dependencies. * * @parameter default-value="${project.remoteProjectRepositories}" * @readonly */ protected List<RemoteRepository> projectRepos; /** * @component */ private MavenProjectHelper projectHelper; /** * @parameter expression="${sync.info.file}" default-value="sync.info" */ private String syncInfo; /** * If <code>true</code> the build fails if it does not find a sync.info file in the root directory * * @parameter expression="${xcode.failOnMissingSyncInfo}" default-value="false" */ private boolean failOnMissingSyncInfo; /** * If <code>true</code> the codesign --verify will be called with --no-strict option * * @parameter expression="${xcode.noStrictVerify}" */ private String noStrictVerify; /** * If <code>true</code> confidential information is removed from artifacts to be released. * * @parameter expression="${xcode.hideConfidentialInformation}" default-value="true" */ private boolean hideConfidentialInformation; @Override public void execute() throws MojoExecutionException, MojoFailureException { final File syncInfoFile = new File(mavenSession.getExecutionRootDirectory(), syncInfo); if (!syncInfoFile.exists()) { if (failOnMissingSyncInfo) { throw new MojoExecutionException("Sync info file '" + syncInfoFile.getAbsolutePath() + "' not found. Please configure your SCM plugin accordingly."); } getLog().info("The optional sync info file '" + syncInfoFile.getAbsolutePath() + "' not found. Cannot attach versions.xml to build results."); return; } getLog().info( "Sync info file found: '" + syncInfoFile.getAbsolutePath() + "'. Creating versions.xml file."); final File versionsXmlFile = new File(project.getBuild().getDirectory(), "versions.xml"); FileOutputStream os = null; try { os = new FileOutputStream(versionsXmlFile); new VersionInfoXmlManager().createVersionInfoFile(project.getGroupId(), project.getArtifactId(), project.getVersion(), syncInfoFile, getDependencies(), os); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } finally { IOUtils.closeQuietly(os); } final File versionsPlistFile = new File(project.getBuild().getDirectory(), "versions.plist"); if (versionsPlistFile.exists()) { if (!versionsPlistFile.delete()) { throw new IllegalStateException( String.format("Cannot delete already existing plist file (%s)", versionsPlistFile)); } } try { new VersionInfoPListManager().createVersionInfoPlistFile(project.getGroupId(), project.getArtifactId(), project.getVersion(), syncInfoFile, getDependencies(), versionsPlistFile, hideConfidentialInformation); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } try { if (PackagingType.getByMavenType(packaging) == PackagingType.APP) { try { copyVersionsFilesAndSign(); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } catch (ExecutionResultVerificationException e) { throw new MojoExecutionException(e.getMessage(), e); } catch (XCodeException e) { throw new MojoExecutionException(e.getMessage(), e); } } } catch (PackagingType.UnknownPackagingTypeException ex) { getLog().warn("Unknown packaing type detected.", ex); } projectHelper.attachArtifact(project, "xml", "versions", versionsXmlFile); getLog().info("versions.xml '" + versionsXmlFile + " attached as additional artifact."); } private void copyVersionsFilesAndSign() throws IOException, ExecutionResultVerificationException, XCodeException, MojoExecutionException { for (final String configuration : getConfigurations()) { for (final String sdk : getSDKs()) { if (sdk.startsWith("iphoneos")) { File versionsXmlInBuild = new File(project.getBuild().getDirectory(), "versions.xml"); File versionsPListInBuild = new File(project.getBuild().getDirectory(), "versions.plist"); File rootDir = XCodeBuildLayout.getAppFolder(getXCodeCompileDirectory(), configuration, sdk); String productName = getProductName(configuration, sdk); File appFolder = new File(rootDir, productName + ".app"); File versionsXmlInApp = new File(appFolder, "versions.xml"); File versionsPListInApp = new File(appFolder, "versions.plist"); boolean isCodeSignActive = true; if ((codeSignIdentity == null || codeSignIdentity.isEmpty()) && !codeSigningRequired) isCodeSignActive = false; ExecResult originalCodesignEntitlementsInfo = null; ExecResult originalSecurityCMSMessageInfo = null; if (isCodeSignActive) { CodeSignManager.verify(appFolder, defineNoStrictVerifyBasedOnXcodeVersion()); originalCodesignEntitlementsInfo = CodeSignManager .getCodesignEntitlementsInformation(appFolder); originalSecurityCMSMessageInfo = CodeSignManager.getSecurityCMSInformation(appFolder); } else { getLog().info("CODE_SIGNING_REQUIRED=\"NO\""); getLog().info("value of codeSignIdentity is " + codeSignIdentity); getLog().info("value of codeSigningRequired is " + codeSigningRequired); } try { if (hideConfidentialInformation) { transformVersionsXml(versionsXmlInBuild, versionsXmlInApp); } else { FileUtils.copyFile(versionsXmlInBuild, versionsXmlInApp); } } catch (Exception e) { throw new MojoExecutionException("Could not transform versions.xml: " + e.getMessage(), e); } getLog().info("Versions.xml file copied from: '" + versionsXmlInBuild + " ' to ' " + versionsXmlInApp); FileUtils.copyFile(versionsPListInBuild, versionsPListInApp); getLog().info("Versions.plist file copied from: '" + versionsPListInBuild + " ' to ' " + versionsPListInApp); if (isCodeSignActive) { sign(rootDir, configuration, sdk); final ExecResult resignedCodesignEntitlementsInfo = CodeSignManager .getCodesignEntitlementsInformation(appFolder); final ExecResult resignedSecurityCMSMessageInfo = CodeSignManager .getSecurityCMSInformation(appFolder); CodeSignManager.verify(appFolder, defineNoStrictVerifyBasedOnXcodeVersion()); CodeSignManager.verify(originalCodesignEntitlementsInfo, resignedCodesignEntitlementsInfo); CodeSignManager.verify(originalSecurityCMSMessageInfo, resignedSecurityCMSMessageInfo); } } } } } private boolean defineNoStrictVerifyBasedOnXcodeVersion() throws XCodeException { if (noStrictVerify != null && (noStrictVerify.equalsIgnoreCase("true") || noStrictVerify.equalsIgnoreCase("false"))) { return Boolean.parseBoolean(noStrictVerify); } else { String xCodeVersionString = getXCodeVersionString(); DefaultArtifactVersion version = getVersion(xCodeVersionString); if (checkVersions(version, MIN_XCODE_VERSION_NO_STRICT_VERIFY)) { return DEFAULT_NO_STRICT_VERIFY_FOR_NEW_XCODE; } else { return DEFAULT_NO_STRICT_VERIFY_FOR_OLD_XCODE; } } } void transformVersionsXml(File versionsXmlInBuild, File versionsXmlInApp) throws ParserConfigurationException, SAXException, IOException, TransformerFactoryConfigurationError, TransformerException, XCodeException { final InputStream transformerRule = getClass().getClassLoader() .getResourceAsStream("versionInfoCensorTransformation.xml"); if (transformerRule == null) { throw new XCodeException("Could not read transformer rule."); } try { final Transformer transformer = TransformerFactory.newInstance() .newTransformer(new StreamSource(transformerRule)); transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.transform(new StreamSource(versionsXmlInBuild), new StreamResult(versionsXmlInApp)); } finally { IOUtils.closeQuietly(transformerRule); } } private void sign(File rootDir, String configuration, String sdk) throws IOException, XCodeException { String csi = (codeSignIdentity != null && codeSignIdentity.trim().length() > 0) ? codeSignIdentity : EffectiveBuildSettings.getBuildSetting( getXCodeContext(XCodeContext.SourceCodeLocation.WORKING_COPY, configuration, sdk), EffectiveBuildSettings.CODE_SIGN_IDENTITY); File appFolder = new File(EffectiveBuildSettings.getBuildSetting( getXCodeContext(XCodeContext.SourceCodeLocation.WORKING_COPY, configuration, sdk), EffectiveBuildSettings.CODESIGNING_FOLDER_PATH)); CodeSignManager.sign(csi, appFolder, true); getLog().info( "value of codeSignIdentity choosed for resign app: " + EffectiveBuildSettings.CODE_SIGN_IDENTITY); getLog().info("value of codeSigningRequired during resign " + codeSigningRequired); } private List<Dependency> getDependencies() throws IOException { List<Dependency> result = new ArrayList<Dependency>(); for (@SuppressWarnings("rawtypes") final Iterator it = project.getDependencyArtifacts().iterator(); it.hasNext();) { final Artifact mainArtifact = (Artifact) it.next(); try { org.sonatype.aether.artifact.Artifact sideArtifact = new XCodeDownloadManager(projectRepos, repoSystem, repoSession).resolveSideArtifact(mainArtifact, "versions", "xml"); getLog().info("Version information retrieved for artifact: " + mainArtifact); addParsedVersionsXmlDependency(result, sideArtifact); } catch (SideArtifactNotFoundException e) { getLog().warn("Could not retrieve version information for artifact:" + mainArtifact); } } return result; } void addParsedVersionsXmlDependency(List<Dependency> result, org.sonatype.aether.artifact.Artifact sideArtifact) throws IOException { try { result.add(VersionInfoXmlManager.parseDependency(sideArtifact.getFile())); } catch (SAXException e) { getLog().warn(format( "Version file '%s' for artifact '%s' contains invalid content (Non parsable XML). Ignoring this file.", (sideArtifact.getFile() != null ? sideArtifact.getFile() : "<n/a>"), sideArtifact)); } catch (JAXBException e) { getLog().warn(format( "Version file '%s' for artifact '%s' contains invalid content (Scheme violation). Ignoring this file.", (sideArtifact.getFile() != null ? sideArtifact.getFile() : "<n/a>"), sideArtifact)); } } }