/* Copyright (C) 2009 Mobile Sorcery AB This program is free software; you can redistribute it and/or modify it under the terms of the Eclipse Public License v1.0. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License v1.0 for more details. You should have received a copy of the Eclipse Public License v1.0 along with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html */ package com.mobilesorcery.sdk.builder.iphoneos; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.ui.preferences.ScopedPreferenceStore; import com.mobilesorcery.sdk.core.BuildResult; import com.mobilesorcery.sdk.core.CommandLineBuilder; import com.mobilesorcery.sdk.core.CoreMoSyncPlugin; import com.mobilesorcery.sdk.core.DefaultPackager; import com.mobilesorcery.sdk.core.IBuildConfiguration; import com.mobilesorcery.sdk.core.IBuildResult; import com.mobilesorcery.sdk.core.IBuildSession; import com.mobilesorcery.sdk.core.IBuildVariant; import com.mobilesorcery.sdk.core.IFileTreeDiff; import com.mobilesorcery.sdk.core.IProcessConsole; import com.mobilesorcery.sdk.core.MoSyncBuilder; import com.mobilesorcery.sdk.core.MoSyncProject; import com.mobilesorcery.sdk.core.PackageToolPackager; import com.mobilesorcery.sdk.core.ParameterResolverException; import com.mobilesorcery.sdk.core.PropertyUtil; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.core.Version; import com.mobilesorcery.sdk.profiles.IProfile; /** * Plugin entry point. Is responsible for creating xcode * project for iphone os devices. * * @author Ali Mosavian */ public class IPhoneOSPackager extends PackageToolPackager { public final static String ID = "com.mobilesorcery.sdk.build.ios.packager"; private static final String PROJECT_FILE = "project"; @Override public void createPackage(MoSyncProject project, IBuildSession session, IBuildVariant variant, IFileTreeDiff diff, IBuildResult buildResult) throws CoreException { try { DefaultPackager intern = new DefaultPackager(project, variant); super.createPackage(project, session, variant, diff, buildResult); if (shouldUseProvisioning(project, variant)) { executeProvisioning(intern, project, variant); } buildResult.setBuildResult(computeBuildResult(project, variant)); // Notify user if we did not build the generated project and say why if (!XCodeBuild.isMac()) { intern.getConsole().addMessage("Xcode building only available in Mac OS X, will not build generated project"); } else if (!shouldBuildWithXcodePref() && !isSimulatorBuild(variant)) { intern.getConsole().addMessage("Xcode building disabled, will not build generated project"); } else if (!XCodeBuild.getDefault().isValid()) { intern.getConsole().addMessage("No Xcode, will not build generated project"); } } catch (Exception e) { buildResult.addError(e.getMessage()); } } private void executeProvisioning(DefaultPackager intern, MoSyncProject project, IBuildVariant variant) throws IOException, CoreException { CommandLineBuilder commandLine = new CommandLineBuilder("xcrun", true); SDK sdk = getSDK(project, variant); commandLine.flag("-sdk").with(sdk.getId()); commandLine.flag("PackageApplication"); File appFile = getAppFile(project, variant); commandLine.flag("-v").with(appFile); File ipaFile = getIpaFile(project, variant); commandLine.flag("-o").with(ipaFile); commandLine.flag("--sign").with(getCertificate(project, variant)); commandLine.flag("--embed").with(project.getProperty(PropertyInitializer.IOS_PROVISIONING_FILE)); intern.runCommandLine(commandLine.asArray(), commandLine.toHiddenString()); } @Override public String getGenerateMode(IProfile profile) { return BUILD_GEN_CPP_MODE; } private static boolean shouldBuildWithXcodePref() { return !Activator.getDefault().getPreferenceStore().getBoolean(Activator.ONLY_GENERATE_XCODE_PROJECT); } private static boolean shouldBuildWithXcode(MoSyncProject project, IBuildVariant variant) throws CoreException { boolean isSimulatorSDK = false; boolean isValid = XCodeBuild.getDefault().isValid(); if (isValid) { SDK sdk = getSDK(project, variant); isSimulatorSDK = sdk != null && sdk.isSimulatorSDK(); } boolean shouldBuild = isValid && (isSimulatorSDK || shouldBuildWithXcodePref()); return shouldBuild; } @Override protected Map<String, List<File>> computeBuildResult(MoSyncProject project, IBuildVariant variant) throws CoreException { File xcodeProject = getXcodeProject(project, variant); if (shouldBuildWithXcode(project, variant)) { File outputAppFile = getAppFile(project, variant); File appFile = null; if (shouldUseProvisioning(project, variant)) { appFile = outputAppFile; outputAppFile = getIpaFile(project, variant); } Map<String, List<File>> buildResult = createBuildResult(outputAppFile); if (appFile != null) { buildResult.put("appfile", Arrays.asList(appFile)); } return buildResult; } else { Map<String, List<File>> buildResult = new HashMap<String, List<File>>(); buildResult.put(PROJECT_FILE, Arrays.asList(xcodeProject)); return buildResult; } } private File getIpaFile(MoSyncProject project, IBuildVariant variant) throws CoreException { File appFile = getAppFile(project, variant); return new File(Util.replaceExtension(appFile.getAbsolutePath(), "ipa")); } private File getAppFile(MoSyncProject project, IBuildVariant variant) throws CoreException { File xcodeProject = getXcodeProject(project, variant); DefaultPackager packager = new DefaultPackager(project, variant); String target = getXcodeTarget(project, variant); SDK sdk = getSDK(project, variant); String xcodeTarget = target + "-" + sdk.getSDKType(); String appFileName = packager.resolve("/%app-name%.app").replace(" ", ""); // Hm, is this always true...? File appFile = packager.resolveFile(xcodeProject.getAbsolutePath() + "/build/" + xcodeTarget + appFileName); return appFile; } private File getXcodeProject(MoSyncProject project, IBuildVariant variant) { DefaultPackager packager = new DefaultPackager(project, variant); File xcodeProject = packager.resolveFile( "%package-output-dir%/xcode-proj" ); return xcodeProject; } public static boolean shouldUseProvisioning(MoSyncProject project, IBuildVariant variant) throws CoreException { return XCodeBuild.isMac() && shouldBuildWithXcode(project, variant) && !isSimulatorBuild(variant) && !Util.isEmpty(project.getProperty(PropertyInitializer.IOS_PROVISIONING_FILE)); } private String getXcodeTarget(MoSyncProject project, IBuildVariant variant) { // Kind of hard-coded in the XCode project template. String cfgId = variant.getConfigurationId(); IBuildConfiguration cfg = project.getBuildConfiguration(cfgId); boolean isDebugBuild = cfg != null && cfg.getTypes().contains(IBuildConfiguration.DEBUG_TYPE); String target = isDebugBuild ? "Debug" : "Release"; return target; } private static boolean isSimulatorBuild(IBuildVariant variant) { return variant.getSpecifiers().containsKey(Activator.IOS_SIMULATOR_SPECIFIER); } private static SDK getSDK(MoSyncProject project, IBuildVariant variant) throws CoreException { int sdkType = isSimulatorBuild(variant) ? XCodeBuild.IOS_SIMULATOR_SDKS : XCodeBuild.IOS_SDKS; SDK sdk = Activator.getDefault().getSDK(project, sdkType); if (sdk == null) { throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "No simulator SDK found, cannot build")); } return sdk; } @Override protected void addPlatformSpecifics(MoSyncProject project, IBuildVariant variant, CommandLineBuilder commandLine) throws Exception { DefaultPackager internal = new DefaultPackager(project, variant); // We do not yet support configuration specific certs. String cert = getCertificate(project, variant); commandLine.flag("--ios-cert").with(cert); String version = internal.get(DefaultPackager.APP_VERSION); String ver = new Version(version).asCanonicalString(Version.MICRO); commandLine.flag("--version").with(ver); String bundleId = project.getProperty(PropertyInitializer.IOS_BUNDLE_IDENTIFIER); commandLine.flag("--ios-bundle-id").with(bundleId); IMessageProvider message = Activator.getDefault().validateBundleIdentifier(internal.resolve(bundleId), false); if (message.getMessageType() == IMessageProvider.ERROR) { throw new IllegalArgumentException(message.getMessage()); } else if (message.getMessageType() == IMessageProvider.WARNING) { getBuildConsole().addMessage(IProcessConsole.ERR, MessageFormat.format("WARNING: {0}", message.getMessage())); } if (!shouldBuildWithXcode(project, variant)) { commandLine.flag("--ios-project-only"); } else { SDK sdk = getSDK(project, variant); commandLine.flag("--ios-sdk").with(sdk.getId()); String target = getXcodeTarget(project, variant); commandLine.flag("--ios-xcode-target").with(target); } commandLine.flag("--cpp-output").with(internal.resolveFile("%program-output%").getParent()); } public static String getCertificate(MoSyncProject project, IBuildVariant variant) { return PropertyUtil.getBoolean(project, PropertyInitializer.IPHONE_PROJECT_SPECIFIC_CERT) ? project.getProperty(PropertyInitializer.IPHONE_CERT): Activator.getDefault().getPreferenceStore().getString(PropertyInitializer.IPHONE_CERT); } protected void addNativePlatformSpecifics(MoSyncProject project, IBuildVariant variant, CommandLineBuilder commandLine) throws Exception { SDK sdk = getSDK(project, variant); commandLine.flag("--ios-sdk").with(sdk.getId()); commandLine.flag("--ios-sdk-version").with(sdk.getVersion().asCanonicalString()); String target = getXcodeTarget(project, variant); commandLine.flag("--ios-xcode-target").with(target); commandLine.flag("--ios-xcode-location").with(XCodeSelect.getInstance().getCurrentXCodePath()); } protected boolean supportsOutputType(String outputType) { return super.supportsOutputType(outputType) || MoSyncBuilder.OUTPUT_TYPE_NATIVE_COMPILE.equals(outputType); } }