package com.mobilesorcery.sdk.builder.iphoneos; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import com.mobilesorcery.sdk.core.AbstractTool; import com.mobilesorcery.sdk.core.CollectingLineHandler; import com.mobilesorcery.sdk.core.CoreMoSyncPlugin; import com.mobilesorcery.sdk.core.IProcessConsole; import com.mobilesorcery.sdk.core.LineReader.LineAdapter; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.core.Version; public class XCodeBuild extends AbstractTool { private static class XCodeBuildHandler extends LineAdapter { private final static Pattern XCODE_VERSION_PATTERN = Pattern .compile(".*?(\\d.*)"); Version version = null; @Override public void newLine(String line) { if (version == null || !version.isValid()) { Matcher matcher = XCODE_VERSION_PATTERN.matcher(line); if (matcher.matches()) { String versionStr = matcher.group(1); version = new Version(versionStr); } } } public Version getVersion() { return version; } } public static final int IOS_SDKS = 1 << 0; public static final int IOS_SIMULATOR_SDKS = 1 << 1; public static final int ALL_SDKS = IOS_SDKS | IOS_SIMULATOR_SDKS; private static XCodeBuild instance = null; private final String pathToCommand; private boolean canExecute = false; private ArrayList<SDK> cachedSDKs; private Version version; /** * Returns the xcodebuild tool with the default path (ie it is expected to * be added to the system path, and modified using the xcode-select command) * * @return */ public static synchronized XCodeBuild getDefault() { if (instance == null) { String xcodeBuildCommand = System.getProperty("XCODEBUILD_COMMAND"); instance = new XCodeBuild(xcodeBuildCommand == null ? "xcodebuild" : xcodeBuildCommand); } return instance; } public static void refreshDefault() { instance = null; getDefault(); } private XCodeBuild(String pathToCommand) { super(null); this.pathToCommand = pathToCommand; init(); } private void init() { if (isMac()) { try { XCodeBuildHandler handler = new XCodeBuildHandler(); execute(new String[] { pathToCommand, "-version" }, handler, handler, CoreMoSyncPlugin.LOG_CONSOLE_NAME, false); this.version = handler.getVersion(); this.canExecute = true; } catch (CoreException e) { // Cannot execute. } } } public boolean canExecute() { return canExecute; } /** * Builds an Xcode project * * @param pathToXcodeProject * @param sdk * The sdk to use for building, or <code>null</code> for the * default. * @return The path to the built app * @throws CoreException * If the build for some reason failed. */ public void build(IPath pathToXcodeProject, String target, String sdk) throws CoreException { chdir(pathToXcodeProject.toFile()); ArrayList<String> cmd = new ArrayList<String>(); cmd.add(pathToCommand); cmd.add("-project"); // XCode < 4.0 does not allow for absolute paths here. cmd.add("%project-name%.xcodeproj"); if (!Util.isEmpty(sdk)) { cmd.add("-sdk"); cmd.add(sdk); } if (execute(cmd.toArray(new String[cmd.size()]), null, null, CoreMoSyncPlugin.LOG_CONSOLE_NAME, false) != 0) { throw new CoreException(new Status(IStatus.ERROR, "com.mobilesorcery.sdk.builder.iphoneos", "xcodebuild failed to build")); } } @Override protected String getToolName() { return "xcodebuild"; } @Override public boolean isValid() { return canExecute && super.isValid(); } /** * Returns the list of available SDKs, or <code>null</code> if unable to * extract them. * * @param sdkTypes * A list of OR'ed sdk types. An sdk type is either * {@link #IOS_SDKS} or {@link #IOS_SIMULATOR_SDKS}. * @return The list of matching SDKs */ public synchronized List<SDK> listSDKs(int sdkTypes) { if (cachedSDKs == null) { cachedSDKs = new ArrayList<SDK>(); CollectingLineHandler lh = new CollectingLineHandler(); try { execute(new String[] { pathToCommand, "-showsdks" }, lh, lh, CoreMoSyncPlugin.LOG_CONSOLE_NAME, false); List<String> sdksOutput = lh.getLines(); cachedSDKs.addAll(extractSDKs(sdksOutput)); } catch (CoreException e) { // Do nothing but log. CoreMoSyncPlugin.getDefault().log(e); } } return filterSDKs(cachedSDKs, sdkTypes); } private List<SDK> filterSDKs(ArrayList<SDK> sdks, int sdkTypes) { ArrayList<SDK> result = new ArrayList<SDK>(); for (SDK sdk : sdks) { if (sdk.isSimulatorSDK() && (sdkTypes & IOS_SIMULATOR_SDKS) != 0) { result.add(sdk); } if (sdk.isIOSSDK() && (sdkTypes & IOS_SDKS) != 0) { result.add(sdk); } } return result; } private List<SDK> extractSDKs(List<String> lines) { ArrayList<SDK> result = new ArrayList<SDK>(); for (String line : lines) { if (line.contains("-sdk")) { String[] components = line.split("-sdk", 2); if (components.length == 2) { String name = components[0].trim(); String id = components[1].trim(); result.add(new SDK(id, name)); } } } return result; } /** * Returns a default SDK for the given SDK type. * * @param sdkType * Either {@link #IOS_SDKS} or {@link #IOS_SIMULATOR_SDKS}. * @return */ public SDK getDefaultSDK(int sdkType) { List<SDK> sdks = listSDKs(sdkType); Version bestMatch = null; SDK bestMatchSDK = null; for (SDK sdk : sdks) { Version version = sdk.getVersion(); if (bestMatch == null || version.isNewer(bestMatch)) { bestMatch = version; bestMatchSDK = sdk; } } return bestMatchSDK; } /** * Returns the SDK with a specific id * * @param sdkId * Returns the first matching SDK with id <code>sdkId</code>. * @return <code>null</code> if no match was found */ public SDK getSDK(String sdkId) { if (Util.isEmpty(sdkId)) { return null; } for (SDK sdk : listSDKs(ALL_SDKS)) { if (sdkId.equals(sdk.getId())) { return sdk; } } return null; } public void refresh() { cachedSDKs = null; } public Version getVersion() { return version; } }