XCodePackageManager.java
/*
* #%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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.archiver.Archiver;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
class XCodePackageManager
{
private final static Logger LOGGER = LogManager.getLogManager().getLogger(XCodePluginLogger.getLoggerName());
final ArchiverManager archiverManager;
final MavenProjectHelper projectHelper;
private static final String ZIPPED_BUNDLE_SUFFIX = "xcode-bundle-zip";
XCodePackageManager(final ArchiverManager archiverManager, final MavenProjectHelper projectHelper)
{
this.archiverManager = archiverManager;
this.projectHelper = projectHelper;
}
/**
* Packages all the artifacts. The main artifact is set and all side artifacts are attached for
* deployment.
*
* @param bundles
*
* @param buildDir
*/
void packageArtifacts(final File compileDir, final MavenProject project, final Set<String> bundles)
throws IOException, XCodeException
{
File mainArtifact = createMainArtifactFile(project);
attachBundle(compileDir, project, bundles, mainArtifact);
final File mainArtifactFile = archiveMainArtifact(project, mainArtifact);
setMainArtifact(project, mainArtifactFile);
}
private void attachBundle(File compileDir, MavenProject project, Set<String> bundles, File mainArtifact)
throws IOException
{
final Set<String> bundleNames = new HashSet<String>();
for (String bundleName : bundles) {
File bundleDirectory = XCodeBuildLayout.getBundleDirectory(compileDir, bundleName);
if (!bundleDirectory.exists()) {
LOGGER.info("Bundle directory '" + bundleDirectory + "' does not exist. Bundle will not be attached.");
continue;
}
final File bundleFile = new File(new File(project.getBuild().getDirectory()), bundleName + ".bundle");
try {
archive("zip", bundleDirectory, bundleFile, new String[] { "**/*" }, null);
LOGGER.info("Bundle zip file created (" + bundleFile + ")");
}
catch (XCodeException ex) {
throw new RuntimeException("Could not archive header directory '" + bundleDirectory + "'", ex);
}
catch (NoSuchArchiverException ex) {
throw new RuntimeException("Could not archive header directory '" + bundleDirectory + "'", ex);
}
String escapedBundleName = escapeBundleName(bundleName);
prepareBundleFileForDeployment(project, bundleFile, escapedBundleName);
bundleNames.add(getBundleReference(project, escapedBundleName));
}
addBundleInfoToMainArtifact(bundleNames, new File(mainArtifact, "bundles.txt"));
}
private String getBundleReference(MavenProject project, String escapedBundleName)
{
return GAVUtil.toColonNotation(project.getGroupId(), project.getArtifactId(), project.getVersion(),
ZIPPED_BUNDLE_SUFFIX,
escapedBundleName);
}
private File createMainArtifactFile(final MavenProject project) throws IOException
{
File mainArtifact = FolderLayout.getFolderForExtractedMainArtifact(project);
if (mainArtifact.exists())
com.sap.prd.mobile.ios.mios.FileUtils.deleteDirectory(mainArtifact);
if (!mainArtifact.mkdirs())
throw new IOException("Could not create directory '" + mainArtifact + "'.");
FileUtils.writeStringToFile(new File(mainArtifact, "README.TXT"),
"This zip file may contain additonal information about the depoyed artifacts. \n");
return mainArtifact;
}
private File archiveMainArtifact(final MavenProject project, File mainArtifact) throws IOException
{
final File mainArtifactTarFile = new File(new File(project.getBuild().getDirectory()), "main.artifact.tar");
try {
archive("tar", mainArtifact, mainArtifactTarFile, new String[] { "**/*" }, null);
LOGGER.info("header tar file created (" + mainArtifactTarFile + ")");
}
catch (XCodeException ex) {
throw new RuntimeException("Could not archive main artifact directory '" + mainArtifact + "'", ex);
}
catch (NoSuchArchiverException ex) {
throw new RuntimeException("Could not archive main artifact directory '" + mainArtifact + "'", ex);
}
return mainArtifactTarFile;
}
private void setMainArtifact(final MavenProject project, final File mainArtifactTarFile)
{
project.getArtifact().setFile(mainArtifactTarFile);
LOGGER.info("Main artifact file '" + mainArtifactTarFile + "' attached for " + project.getArtifact());
}
void packageHeaders(final XCodeContext xcodeContext, MavenProject project,
String relativeAlternatePublicHeaderFolderPath) throws IOException, XCodeException
{
final File publicHeaderFolderPath = getPublicHeaderFolderPath(EffectiveBuildSettings.getBuildSetting(xcodeContext, EffectiveBuildSettings.BUILT_PRODUCTS_DIR),
EffectiveBuildSettings.getBuildSetting(xcodeContext, EffectiveBuildSettings.PUBLIC_HEADERS_FOLDER_PATH),
relativeAlternatePublicHeaderFolderPath);
if (!publicHeaderFolderPath.canRead()) {
LOGGER.warning("Public header folder path '" + publicHeaderFolderPath + "' cannot be read. Unable to package headers.");
return;
}
final File headersFile = new File(new File(new File(project.getBuild().getDirectory()),
xcodeContext.getConfiguration() + "-" + xcodeContext.getSDK()),
"headers.tar");
try {
archive("tar", publicHeaderFolderPath, headersFile, new String[] { "**/*.h" }, null);
LOGGER.info("header tar file created (" + headersFile + ")");
}
catch (XCodeException ex) {
throw new RuntimeException("Could not archive header directory '" + publicHeaderFolderPath + "'", ex);
}
catch (NoSuchArchiverException ex) {
throw new RuntimeException("Could not archive header directory '" + publicHeaderFolderPath + "'", ex);
}
prepareHeaderFileForDeployment(project, xcodeContext.getConfiguration(), xcodeContext.getSDK(), headersFile);
}
private void prepareHeaderFileForDeployment(final MavenProject mavenProject, final String configuration,
final String sdk, final File headersFile)
{
projectHelper.attachArtifact(mavenProject, "headers.tar", configuration + "-" + sdk, headersFile);
}
private void prepareBundleFileForDeployment(MavenProject mavenProject, File bundleFile, String escapedBundleName)
{
projectHelper.attachArtifact(mavenProject, ZIPPED_BUNDLE_SUFFIX, escapedBundleName, bundleFile);
}
String escapeBundleName(String bundleName)
{
return bundleName.replaceAll("/", "~");
}
private void addBundleInfoToMainArtifact(Set<String> bundleNames, File bundlesFile) throws IOException
{
final PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(bundlesFile), Charset.defaultCharset().name()));
try {
for (final String bundleName : bundleNames) {
pw.println(bundleName);
}
}
finally {
pw.close();
}
}
static void attachLibrary(final XCodeContext xcodeContext, File buildDir,
final MavenProject project, final MavenProjectHelper projectHelper)
{
final File fatBinary = XCodeBuildLayout.getBinary(buildDir, xcodeContext.getConfiguration(), xcodeContext.getSDK(),
project.getArtifactId());
if (!fatBinary.exists())
throw new RuntimeException(fatBinary + " should be attached but does not exist.");
final String classifier = xcodeContext.getConfiguration() + "-" + xcodeContext.getSDK();
projectHelper.attachArtifact(project, "a", classifier, fatBinary);
LOGGER.info("Archive file '" + fatBinary + "' attached as side artifact for '" + project.getArtifact()
+ "' with classifier '" + classifier + "'.");
}
private final void archive(final String archiverType, final File rootDir, final File archive,
final String[] includes, final String[] excludes) throws NoSuchArchiverException,
IOException, XCodeException
{
try {
final Archiver archiver = archiverManager.getArchiver(archiverType);
archiver.addDirectory(rootDir, includes, excludes);
archiver.setDestFile(archive);
archiver.createArchive();
}
catch (ArchiverException ex) {
throw new XCodeException("Could not archive folder '" + rootDir + "' into '" + archive + "': " + ex.getMessage(),
ex);
}
}
static File getPublicHeaderFolderPath(String builtProductDirInsideXcodeProject, String publicHeaderFolderPathInsideXcodeProject,
String relativeAlternatePublicHeaderFolderPath)
throws XCodeException
{
final String relativePublicHeaderFolderPathInXcodeProject = publicHeaderFolderPathInsideXcodeProject;
final String relativePublicHeaderFolderPath;
if (StringUtils.isEmpty(relativeAlternatePublicHeaderFolderPath)) {
relativePublicHeaderFolderPath = relativePublicHeaderFolderPathInXcodeProject;
LOGGER.info(
"Using public header folder path as it is configured in the xcode project: '"
+ relativePublicHeaderFolderPathInXcodeProject + "'.");
}
else {
relativePublicHeaderFolderPath = relativeAlternatePublicHeaderFolderPath;
if (!com.sap.prd.mobile.ios.mios.FileUtils.isChild(
new File(
com.sap.prd.mobile.ios.mios.FileUtils.ensureLeadingSlash(relativeAlternatePublicHeaderFolderPath)),
new File(
com.sap.prd.mobile.ios.mios.FileUtils
.ensureLeadingSlash(relativePublicHeaderFolderPathInXcodeProject))))
throw new InvalidAlternatePublicHeaderPathException(
"Public header folder path configured on the level of xcode-maven-plugin configuration ("
+ relativeAlternatePublicHeaderFolderPath
+ ") is not a parent folder of the public header path configured inside the xcode project ("
+ relativePublicHeaderFolderPathInXcodeProject + ").");
LOGGER.info(
"Using public header folder path as it is defined inside the xcode-maven-plugin ("
+ relativeAlternatePublicHeaderFolderPath
+ "). In the xcode project '" + relativePublicHeaderFolderPathInXcodeProject
+ "' is configured as public header folder path.");
}
return new File(builtProductDirInsideXcodeProject,
com.sap.prd.mobile.ios.mios.FileUtils.ensureLeadingSlash(relativePublicHeaderFolderPath));
}
}