XCodePrepareBuildManager.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 static com.sap.prd.mobile.ios.mios.FileUtils.mkdirs;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.repository.RemoteRepository;
class XCodePrepareBuildManager
{
private final static Logger LOGGER = LogManager.getLogManager().getLogger(XCodePluginLogger.getLoggerName());
private final static String TYPE_HEADERS = "headers.tar", TYPE_ARCHIVE = "a";
private final ArchiverManager archiverManager;
private final XCodeDownloadManager downloadManager;
private final boolean useSymbolicLinks;
private Map<String, String> additionalPackagingTypes;
private boolean preferFatLibs;
XCodePrepareBuildManager(final ArchiverManager archiverManager,
final RepositorySystemSession repoSystemSession, final RepositorySystem repoSystem,
final List<RemoteRepository> projectRepos, final boolean useSymbolicLinks,
final Map<String, String> additionalPackagingTypes)
{
this.archiverManager = archiverManager;
this.downloadManager = new XCodeDownloadManager(projectRepos, repoSystem, repoSystemSession);
this.useSymbolicLinks = useSymbolicLinks;
if (additionalPackagingTypes == null) {
this.additionalPackagingTypes = Collections.emptyMap();
}
else {
this.additionalPackagingTypes = additionalPackagingTypes;
}
}
public XCodePrepareBuildManager setPreferFalLibs(boolean preferFatLibs)
{
this.preferFatLibs = preferFatLibs;
return this;
}
void prepareBuild(final MavenProject project, Set<String> configurations,
final Set<String> sdks) throws MojoExecutionException, XCodeException, IOException
{
prepareRootFolders(project, configurations, sdks);
final Iterator<Artifact> dependentArtifacts = project.getArtifacts().iterator();
if (!dependentArtifacts.hasNext()) {
LOGGER.info("No dependencies found.");
}
while (dependentArtifacts.hasNext()) {
final Artifact mainArtifact = (Artifact) dependentArtifacts.next();
LOGGER.info("Preparing dependency: " + mainArtifact.getId());
if (PackagingType.LIB.getMavenPackaging().equals(mainArtifact.getType())) {
prepareLibrary(project, configurations, sdks, mainArtifact);
}
else if (PackagingType.FRAMEWORK.getMavenPackaging().equals(mainArtifact.getType())) {
prepareFramework(project, mainArtifact, configurations);
}
else if (additionalPackagingTypes.keySet().contains(mainArtifact.getType())) {
final PackagingTypeAction packagingTypeAction = PackagingTypeAction.valueOf(additionalPackagingTypes
.get(mainArtifact.getType()));
LOGGER.info("Packaging type '" + mainArtifact.getType() + "' found in pom. Action: " + packagingTypeAction);
packagingTypeAction.perform(archiverManager, project, mainArtifact);
}
else {
LOGGER.warning("Unknown dependency type detected: '" + mainArtifact.getType() + "'. The corresponding dependency '"
+ mainArtifact.getGroupId() + ":" + mainArtifact.getArtifactId() + ":" + mainArtifact.getVersion()
+ "' will be ignored.");
}
}
}
private void prepareLibrary(final MavenProject project,
Set<String> configurations, final Set<String> sdks,
final Artifact mainArtifact) throws MojoExecutionException,
IOException, XCodeException
{
for (final String xcodeConfiguration : configurations) {
Map<String, File> thinLibs = new HashMap<String, File>();
for (final String sdk : sdks) {
thinLibs.put(sdk, resolveThinLib(project, xcodeConfiguration, sdk, mainArtifact));
try {
prepareHeaders(project, xcodeConfiguration, sdk, mainArtifact);
}
catch (SideArtifactNotFoundException e) {
LOGGER.info("Headers not found for: '" + mainArtifact.getGroupId() + ":" + mainArtifact.getArtifactId()
+ ":"
+ mainArtifact.getVersion() + ":" + mainArtifact.getType() + "'.");
}
}
File fatLib = resolveFatLib(project, xcodeConfiguration, mainArtifact);
if (thinLibs.values().contains(null)) {
if (fatLib != null) {
provideFatLib(fatLib, project, xcodeConfiguration, mainArtifact, sdks);
}
else {
throw new XCodeException("Neither all thin libs nor fat lib available for " + mainArtifact.getId() + ".");
}
}
else {
if (preferFatLibs && fatLib != null)
provideFatLib(fatLib, project, xcodeConfiguration, mainArtifact, sdks);
else
provideThinLibs(thinLibs, xcodeConfiguration, mainArtifact, project);
}
}
try {
prepareBundles(project, mainArtifact);
}
catch (SideArtifactNotFoundException e) {
LOGGER.info("Bundle not found for: '" + mainArtifact.getGroupId() + ":" + mainArtifact.getArtifactId() + ":"
+ mainArtifact.getVersion() + ":" + mainArtifact.getType() + "'.");
}
}
private static void prepareRootFolders(MavenProject project, Set<String> configurations, Set<String> sdks)
throws IOException
{
mkdirs(FolderLayout.getFolderForExtractedMainArtifact(project));
mkdirs(FolderLayout.getFolderForExtractedBundles(project));
mkdirs(FolderLayout.getFolderForExtractedFrameworks(project));
for (String configuration : configurations) {
mkdirs(FolderLayout.getFolderForExtractedFatLibs(project, configuration));
for (String sdk : sdks) {
mkdirs(FolderLayout.getFolderForExtractedHeaders(project, configuration, sdk));
mkdirs(FolderLayout.getFolderForExtractedLibs(project, configuration, sdk));
}
}
}
private File resolveThinLib(MavenProject project, final String xcodeConfiguration, final String sdk,
final Artifact primaryArtifact)
{
try {
return downloadManager.resolveSideArtifact(primaryArtifact,
xcodeConfiguration + "-" + sdk, TYPE_ARCHIVE).getFile();
}
catch (SideArtifactNotFoundException ex) {
LOGGER.info("Library not found for: " + primaryArtifact.getGroupId() + ":"
+ primaryArtifact.getArtifactId() + ":" + primaryArtifact.getVersion() + ":"
+ primaryArtifact.getClassifier()
+ ":" + primaryArtifact.getType());
return null;
}
}
void provideThinLibs(Map<String, File> thinLibs, String xcodeConfiguration, Artifact mainArtifact,
MavenProject project) throws IOException
{
for (Map.Entry<String, File> e : thinLibs.entrySet()) {
provideThinLib(e.getValue(), project, xcodeConfiguration, e.getKey(), mainArtifact);
}
}
private void prepareBundles(MavenProject project, final Artifact primaryArtifact) throws MojoExecutionException,
SideArtifactNotFoundException, IOException
{
List<String> bundles = readBundleInformation(project, primaryArtifact);
if (bundles == null)
return;
for (String coords : bundles) {
prepareBundle(project, primaryArtifact, coords);
}
}
private void prepareBundle(MavenProject project, final Artifact primaryArtifact, String coords)
throws SideArtifactNotFoundException, MojoExecutionException
{
Artifact bundleArtifact = GAVUtil.getArtifact(coords);
final org.sonatype.aether.artifact.Artifact artifact = downloadManager.resolveSideArtifact(bundleArtifact);
if (artifact != null) {
final File source = artifact.getFile();
final File target = new File(FolderLayout.getFolderForExtractedBundlesWithGA(project,
primaryArtifact.getGroupId(), primaryArtifact.getArtifactId()), bundleArtifact.getClassifier().replaceAll(
"~", File.separator)
+ ".bundle");
createDirectory(target);
com.sap.prd.mobile.ios.mios.FileUtils.unarchive(archiverManager, "zip", source, target);
LOGGER.info("Bundle unarchived from " + source + " to " + target);
}
}
@SuppressWarnings("unchecked")
private List<String> readBundleInformation(MavenProject project, Artifact primaryArtifact) throws IOException
{
final File mainArtifactExtracted = FolderLayout.getFolderForExtractedPrimaryArtifact(project,
primaryArtifact);
if (mainArtifactExtracted.exists())
com.sap.prd.mobile.ios.mios.FileUtils.deleteDirectory(mainArtifactExtracted);
if (!mainArtifactExtracted.mkdirs())
throw new IOException("Cannot create directory for expanded mainartefact of " + primaryArtifact.getGroupId()
+ ":" + primaryArtifact.getArtifactId() + " (" + mainArtifactExtracted + ").");
com.sap.prd.mobile.ios.mios.FileUtils.unarchive(archiverManager, "tar", primaryArtifact.getFile(),
mainArtifactExtracted);
LOGGER.info("Main artifact extracted to '" + mainArtifactExtracted + "'.");
File bundleFile = new File(mainArtifactExtracted, "bundles.txt");
if (!bundleFile.exists())
return null;
try {
return FileUtils.readLines(bundleFile);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
private void provideThinLib(final File source, MavenProject project, final String xcodeConfiguration,
final String sdk, final Artifact primaryArtifact) throws IOException
{
final File target = new File(FolderLayout.getFolderForExtractedLibsWithGA(project, xcodeConfiguration,
sdk,
primaryArtifact.getGroupId(), primaryArtifact.getArtifactId()), getArchiveFileName(primaryArtifact));
if (ArtifactUtils.isSnapshot(primaryArtifact.getVersion()) || !useSymbolicLinks()) {
FileUtils.copyFile(source, target);
}
else {
com.sap.prd.mobile.ios.mios.FileUtils.createSymbolicLink(source, target);
}
}
private File resolveFatLib(MavenProject project, final String xcodeConfiguration, final Artifact primaryArtifact)
{
try {
return downloadManager.resolveSideArtifact(primaryArtifact,
xcodeConfiguration + XCodeFatLibraryMojo.FAT_LIBRARY_CLASSIFIER_SUFFIX,
TYPE_ARCHIVE).getFile();
}
catch (SideArtifactNotFoundException ex) {
LOGGER.info("There does not exist a fat library for the artifact " + primaryArtifact.getId());
return null;
}
}
private void provideFatLib(File source, MavenProject project, final String xcodeConfiguration,
final Artifact primaryArtifact, Set<String> sdks) throws IOException
{
final File target = new File(FolderLayout.getFolderForExtractedFatLibsWithGA(project, xcodeConfiguration,
primaryArtifact.getGroupId(), primaryArtifact.getArtifactId()), getArchiveFileName(primaryArtifact));
if (ArtifactUtils.isSnapshot(primaryArtifact.getVersion()) || !useSymbolicLinks()) {
FileUtils.copyFile(source, target);
}
else {
com.sap.prd.mobile.ios.mios.FileUtils.createSymbolicLink(source, target);
}
final FatLibAnalyzer lipoHelper = new FatLibAnalyzer(target);
//
// TODO improve hard coded strings for iphoneos and iphonesimulator below
//
if (sdks.contains("iphoneos")) {
if (!lipoHelper.containsArmv())
LOGGER.warning("Fat library '" + lipoHelper.getFatLibrary() + "' does not contain a library for armv*.");
else
LOGGER.info("Fat library '" + lipoHelper.getFatLibrary() + "'contains a library for armv*.");
}
else if (sdks.contains("iphonesimulator")) {
if (!lipoHelper.containsI386())
LOGGER.warning("Fat library '" + lipoHelper.getFatLibrary() + "' does not contain a library for i386.");
else
LOGGER.info("Fat library '" + lipoHelper.getFatLibrary() + "'contains a library for i386.");
}
}
private boolean useSymbolicLinks()
{
return this.useSymbolicLinks;
}
private void prepareHeaders(MavenProject project, String xcodeConfiguration,
final String sdk, final Artifact primaryArtifact) throws MojoExecutionException, SideArtifactNotFoundException
{
final org.sonatype.aether.artifact.Artifact headersArtifact = downloadManager.resolveSideArtifact(primaryArtifact,
xcodeConfiguration + "-" + sdk, TYPE_HEADERS);
if (headersArtifact != null) {
final File headersDirectory = FolderLayout.getFolderForExtractedHeadersWithGA(project,
xcodeConfiguration, sdk,
primaryArtifact.getGroupId(), primaryArtifact.getArtifactId());
createDirectory(headersDirectory);
extractHeaders(headersDirectory, headersArtifact.getFile());
}
}
private void prepareFramework(MavenProject project, final Artifact primaryArtifact, Collection<String> configurations)
throws MojoExecutionException
{
for (String configuration : configurations) {
try {
org.sonatype.aether.artifact.Artifact frameworkArtifact = downloadManager.resolveSideArtifact(primaryArtifact,
configuration, Types.FRAMEWORK);
extractFramework(project, primaryArtifact, configuration, frameworkArtifact.getFile());
}
catch (SideArtifactNotFoundException e) {
LOGGER
.warning("Framework '"
+ primaryArtifact
+ "' does not contain configuration specific variant. Will download the generic framework for configuration '"
+ configuration + "'.");
handlePrimaryArtifact(project, primaryArtifact, configurations);
}
}
}
private void extractFramework(MavenProject project, final Artifact primaryArtifact, String configuration,
File frameworkArtifact) throws MojoExecutionException
{
File target = FolderLayout.getFolderForExtractedFrameworkswithGA(project, primaryArtifact.getGroupId(),
primaryArtifact.getArtifactId(), configuration);
createDirectory(target);
try {
extractFileWithShellScript(frameworkArtifact, target, new File(project.getBuild().getDirectory()));
}
catch (IOException ioe) {
throw new MojoExecutionException("Cannot unarchive framework from " + frameworkArtifact + " to "
+ target);
}
}
private void handlePrimaryArtifact(MavenProject project, final Artifact primaryArtifact,
Collection<String> configurations)
throws MojoExecutionException
{
if (primaryArtifact != null) {
final File source = primaryArtifact.getFile();
for (String configuration : configurations) {
final File target = FolderLayout.getFolderForExtractedFrameworkswithGA(project,
primaryArtifact.getGroupId(), primaryArtifact.getArtifactId());
extractFramework(project, primaryArtifact, configuration, source);
try {
extractFileWithShellScript(source, target, new File(project.getBuild().getDirectory()));
}
catch (IOException ioe) {
throw new MojoExecutionException("Cannot unarchive framework from " + source + " to " + target);
}
LOGGER.info("Framework unarchived from " + source + " to " + target);
}
}
}
private static String getArchiveFileName(final Artifact primaryArtifact)
{
return "lib" + primaryArtifact.getArtifactId() + ".a";
}
private void extractHeaders(final File headersDirectory, final File headers)
{
com.sap.prd.mobile.ios.mios.FileUtils.unarchive(archiverManager, "tar", headers, headersDirectory);
}
/**
* Creates a directory. If the directory already exists the directory is deleted beforehand.
*
* @param directory
* @throws MojoExecutionException
*/
private static void createDirectory(final File directory) throws MojoExecutionException
{
try {
com.sap.prd.mobile.ios.mios.FileUtils.deleteDirectory(directory);
}
catch (IOException ex) {
throw new MojoExecutionException("", ex);
}
if (!directory.mkdirs())
throw new MojoExecutionException("Cannot create directory (" + directory + ")");
}
private void extractFileWithShellScript(File sourceFile, File destinationFolder, File tmpFolder) throws IOException
{
File workingDirectory = new File(tmpFolder, "scriptWorkingDir");
workingDirectory.deleteOnExit();
ScriptRunner.copyAndExecuteScript(System.out, "/com/sap/prd/mobile/ios/mios/unzip.sh", workingDirectory,
sourceFile.getCanonicalPath(),
destinationFolder.getCanonicalPath());
}
}