package lime.tools.helpers; import haxe.io.Path; import lime.tools.helpers.PathHelper; import lime.tools.helpers.ProcessHelper; import lime.project.Haxelib; import lime.project.HXProject; import sys.io.Process; import sys.FileSystem; class IOSHelper { private static var initialized = false; public static function build (project:HXProject, workingDirectory:String, additionalArguments:Array = null):Void { initialize (project); var platformName = "iphoneos"; if (project.targetFlags.exists ("simulator")) { platformName = "iphonesimulator"; } var configuration = "Release"; if (project.debug) { configuration = "Debug"; } var iphoneVersion = project.environment.get ("IPHONE_VER"); var commands = [ "-configuration", configuration, "PLATFORM_NAME=" + platformName, "SDKROOT=" + platformName + iphoneVersion ]; if (project.targetFlags.exists("simulator")) { commands.push ("-arch"); commands.push ("i386"); } if (additionalArguments != null) { commands = commands.concat (additionalArguments); } ProcessHelper.runCommand (workingDirectory, "xcodebuild", commands); } public static function getSDKDirectory (project:HXProject):String { initialize (project); var platformName = "iPhoneOS"; if (project.targetFlags.exists ("simulator")) { platformName = "iPhoneSimulator"; } var process = new Process ("xcode-select", [ "--print-path" ]); var directory = process.stdout.readLine (); process.close (); if (directory == "" || directory.indexOf ("Run xcode-select") > -1) { directory = "/Applications/Xcode.app/Contents/Developer"; } directory += "/Platforms/" + platformName + ".platform/Developer/SDKs/" + platformName + project.environment.get ("IPHONE_VER") + ".sdk"; return directory; } private static function getIOSVersion (project:HXProject):Void { if (!project.environment.exists("IPHONE_VER")) { if (!project.environment.exists("DEVELOPER_DIR")) { var proc = new Process("xcode-select", ["--print-path"]); var developer_dir = proc.stdout.readLine(); proc.close(); project.environment.set("DEVELOPER_DIR", developer_dir); } var dev_path = project.environment.get("DEVELOPER_DIR") + "/Platforms/iPhoneOS.platform/Developer/SDKs"; if (FileSystem.exists (dev_path)) { var best = ""; var files = FileSystem.readDirectory (dev_path); var extract_version = ~/^iPhoneOS(.*).sdk$/; for (file in files) { if (extract_version.match (file)) { var ver = extract_version.matched (1); if (ver > best) best = ver; } } if (best != "") project.environment.set ("IPHONE_VER", best); } } } private static function getOSXVersion ():String { var output = ProcessHelper.runProcess ("", "sw_vers", [ "-productVersion" ]); return StringTools.trim (output); } public static function getProvisioningFile ():String { var path = PathHelper.expand ("~/Library/MobileDevice/Provisioning Profiles"); var files = FileSystem.readDirectory (path); for (file in files) { if (Path.extension (file) == "mobileprovision") { return path + "/" + file; } } return ""; } private static function getXcodeVersion ():String { var output = ProcessHelper.runProcess ("", "xcodebuild", [ "-version" ]); var firstLine = output.split ("\n").shift (); return StringTools.trim (firstLine.substring ("Xcode".length, firstLine.length)); } private static function initialize (project:HXProject):Void { if (!initialized) { getIOSVersion (project); initialized = true; } } public static function launch (project:HXProject, workingDirectory:String):Void { initialize (project); var configuration = "Release"; if (project.debug) { configuration = "Debug"; } if (project.targetFlags.exists ("simulator")) { var applicationPath = ""; if (Path.extension (workingDirectory) == "app" || Path.extension (workingDirectory) == "ipa") { applicationPath = workingDirectory; } else { applicationPath = workingDirectory + "/build/" + configuration + "-iphonesimulator/" + project.app.file + ".app"; } var templatePaths = [ PathHelper.combine (PathHelper.getHaxelib (new Haxelib ("lime")), "templates") ].concat (project.templatePaths); var output = ProcessHelper.runProcess ("", "xcrun", [ "simctl", "list", "devices" ]); var lines = output.split ("\n"); var foundSection = false; var device, deviceID; var devices = new Map (); var currentDeviceID = null; for (line in lines) { if (StringTools.startsWith (line, "--")) { if (line.indexOf ("iOS") > -1) { foundSection = true; } else if (foundSection) { break; } } else if (foundSection) { device = StringTools.trim (line); device = device.substring (0, device.indexOf ("(") - 1); device = device.toLowerCase (); device = StringTools.replace (device, " ", "-"); deviceID = line.substring (line.indexOf ("(") + 1, line.indexOf (")")); if (deviceID.indexOf ("inch") > -1) { var startIndex = line.indexOf (")") + 2; deviceID = line.substring (line.indexOf ("(", startIndex) + 1, line.indexOf (")", startIndex)); } devices.set (device, deviceID); if (project.targetFlags.exists (device)) { currentDeviceID = deviceID; break; } } } if (currentDeviceID == null) { if (project.targetFlags.exists ("ipad")) { currentDeviceID = devices.get ("ipad-air"); } else { currentDeviceID = devices.get ("iphone-6"); } } try { ProcessHelper.runProcess ("", "open", [ "-Ra", "iOS Simulator" ], true, false); ProcessHelper.runCommand ("", "open", [ "-a", "iOS Simulator", "--args", "-CurrentDeviceUDID", currentDeviceID ]); } catch (e:Dynamic) { ProcessHelper.runCommand ("", "open", [ "-a", "Simulator", "--args", "-CurrentDeviceUDID", currentDeviceID ]); } waitForDeviceState ("xcrun", [ "simctl", "uninstall", currentDeviceID, project.meta.packageName ]); waitForDeviceState ("xcrun", [ "simctl", "install", currentDeviceID, applicationPath ]); waitForDeviceState ("xcrun", [ "simctl", "launch", currentDeviceID, project.meta.packageName ]); ProcessHelper.runCommand ("", "tail", [ "-F", "~/Library/Logs/CoreSimulator/" + currentDeviceID + "/system.log"]); } else { var applicationPath = ""; if (Path.extension (workingDirectory) == "app" || Path.extension (workingDirectory) == "ipa") { applicationPath = workingDirectory; } else { applicationPath = workingDirectory + "/build/" + configuration + "-iphoneos/" + project.app.file + ".app"; } var templatePaths = [ PathHelper.combine (PathHelper.getHaxelib (new Haxelib ("lime")), "templates") ].concat (project.templatePaths); var launcher = PathHelper.findTemplate (templatePaths, "bin/ios-deploy"); Sys.command ("chmod", [ "+x", launcher ]); var xcodeVersion = getXcodeVersion (); ProcessHelper.runCommand ("", launcher, [ "install", "--noninteractive", "--debug", "--timeout", "5", "--bundle", FileSystem.fullPath (applicationPath) ]); } } public static function sign (project:HXProject, workingDirectory:String, entitlementsPath:String):Void { initialize (project); var configuration = "Release"; if (project.debug) { configuration = "Debug"; } var identity = "iPhone Developer"; if (project.certificate != null && project.certificate.identity != null) { identity = project.certificate.identity; } var commands = [ "-s", identity ]; if (entitlementsPath != null) { commands.push ("--entitlements"); commands.push (entitlementsPath); } var applicationPath = "build/" + configuration + "-iphoneos/" + project.app.file + ".app"; commands.push (applicationPath); ProcessHelper.runCommand (workingDirectory, "codesign", commands, true, true); } private static function waitForDeviceState (command:String, args:Array):Void { var output; while (true) { output = ProcessHelper.runProcess ("", command, args, true, true, true); if (output != null && output.toLowerCase ().indexOf ("invalid device state") > -1) { Sys.sleep (3); } else { break; } } } }