-
-
Save godrei/16ce30f2a42d6544e49f56cadff9ff7b to your computer and use it in GitHub Desktop.
Start iOS Simulator and wait for boot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"fmt" | |
"os/exec" | |
"path/filepath" | |
"regexp" | |
"strconv" | |
"strings" | |
"time" | |
"github.com/bitrise-io/go-utils/command" | |
ver "github.com/hashicorp/go-version" | |
) | |
// SimInfoModel ... | |
type SimInfoModel struct { | |
Name string | |
SimID string | |
Status string | |
StatusOther string | |
} | |
// OSVersionSimInfoPairModel ... | |
type OSVersionSimInfoPairModel struct { | |
OSVersion string | |
SimulatorInfo SimInfoModel | |
} | |
// SimulatorsGroupedByIOSVersionsModel ... | |
type SimulatorsGroupedByIOSVersionsModel map[string][]SimInfoModel | |
// XcodebuildVersionModel ... | |
type XcodebuildVersionModel struct { | |
Version string | |
BuildVersion string | |
MajorVersion int64 | |
} | |
// GetXcodeVersion ... | |
func GetXcodeVersion() (XcodebuildVersionModel, error) { | |
cmd := exec.Command("xcodebuild", "-version") | |
outBytes, err := cmd.CombinedOutput() | |
outStr := string(outBytes) | |
if err != nil { | |
return XcodebuildVersionModel{}, fmt.Errorf("xcodebuild -version failed, err: %s, details: %s", err, outStr) | |
} | |
split := strings.Split(outStr, "\n") | |
if len(split) == 0 { | |
return XcodebuildVersionModel{}, fmt.Errorf("failed to parse xcodebuild version output (%s)", outStr) | |
} | |
xcodebuildVersion := split[0] | |
buildVersion := split[1] | |
split = strings.Split(xcodebuildVersion, " ") | |
if len(split) != 2 { | |
return XcodebuildVersionModel{}, fmt.Errorf("failed to parse xcodebuild version output (%s)", outStr) | |
} | |
version := split[1] | |
split = strings.Split(version, ".") | |
majorVersionStr := split[0] | |
majorVersion, err := strconv.ParseInt(majorVersionStr, 10, 32) | |
if err != nil { | |
return XcodebuildVersionModel{}, fmt.Errorf("failed to parse xcodebuild version output (%s), error: %s", outStr, err) | |
} | |
return XcodebuildVersionModel{ | |
Version: xcodebuildVersion, | |
BuildVersion: buildVersion, | |
MajorVersion: majorVersion, | |
}, nil | |
} | |
// a simulator info line should look like this: | |
// iPhone 5s (EA1C7E48-8137-428C-A0A5-B2C63FF276EB) (Shutdown) | |
// or | |
// iPhone 4s (51B10EBD-C949-49F5-A38B-E658F41640FF) (Shutdown) (unavailable, runtime profile not found) | |
func getSimInfoFromLine(lineStr string) (SimInfoModel, error) { | |
baseInfosExp := regexp.MustCompile(`(?P<deviceName>[a-zA-Z].*[a-zA-Z0-9 -]*) \((?P<simulatorID>[a-zA-Z0-9-]{36})\) \((?P<status>[a-zA-Z]*)\)`) | |
baseInfosRes := baseInfosExp.FindStringSubmatch(lineStr) | |
if baseInfosRes == nil { | |
return SimInfoModel{}, fmt.Errorf("No match found") | |
} | |
simInfo := SimInfoModel{ | |
Name: baseInfosRes[1], | |
SimID: baseInfosRes[2], | |
Status: baseInfosRes[3], | |
} | |
// StatusOther | |
restOfTheLine := lineStr[len(baseInfosRes[0]):] | |
if len(restOfTheLine) > 0 { | |
statusOtherExp := regexp.MustCompile(`\((?P<statusOther>[a-zA-Z ,]*)\)`) | |
statusOtherRes := statusOtherExp.FindStringSubmatch(restOfTheLine) | |
if statusOtherRes != nil { | |
simInfo.StatusOther = statusOtherRes[1] | |
} | |
} | |
return simInfo, nil | |
} | |
func collectAllSimIDs(simctlListOutputToScan string) SimulatorsGroupedByIOSVersionsModel { | |
simulatorsByIOSVersions := SimulatorsGroupedByIOSVersionsModel{} | |
currIOSVersion := "" | |
fscanner := bufio.NewScanner(strings.NewReader(simctlListOutputToScan)) | |
isDevicesSectionFound := false | |
for fscanner.Scan() { | |
aLine := fscanner.Text() | |
if aLine == "== Devices ==" { | |
isDevicesSectionFound = true | |
continue | |
} | |
if !isDevicesSectionFound { | |
continue | |
} | |
if strings.HasPrefix(aLine, "==") { | |
isDevicesSectionFound = false | |
continue | |
} | |
if strings.HasPrefix(aLine, "--") { | |
iosVersionSectionExp := regexp.MustCompile(`-- (?P<iosVersionSection>.*) --`) | |
iosVersionSectionRes := iosVersionSectionExp.FindStringSubmatch(aLine) | |
if iosVersionSectionRes != nil { | |
currIOSVersion = iosVersionSectionRes[1] | |
} | |
continue | |
} | |
// fmt.Println("-> ", aLine) | |
simInfo, err := getSimInfoFromLine(aLine) | |
if err != nil { | |
fmt.Println(" [!] Error scanning the line for Simulator info: ", err) | |
} | |
currIOSVersionSimList := simulatorsByIOSVersions[currIOSVersion] | |
currIOSVersionSimList = append(currIOSVersionSimList, simInfo) | |
simulatorsByIOSVersions[currIOSVersion] = currIOSVersionSimList | |
} | |
return simulatorsByIOSVersions | |
} | |
// Compares sematic versions with 2 components (9.1, 9.2, ...) | |
// Return true if first version is greater then second | |
func isOsVersionGreater(osVersion, otherOsVersion string) (bool, error) { | |
versionPtrs := []*ver.Version{} | |
for _, osVer := range []string{osVersion, otherOsVersion} { | |
osVersionSplit := strings.Split(osVer, " ") | |
if len(osVersionSplit) != 2 { | |
return false, fmt.Errorf("failed to parse version: %s", osVer) | |
} | |
version, err := ver.NewVersion(osVersionSplit[1]) | |
if err != nil { | |
return false, err | |
} | |
versionPtrs = append(versionPtrs, version) | |
} | |
return versionPtrs[0].GreaterThan(versionPtrs[1]), nil | |
} | |
func getLatestOsVersion(desiredPlatform, desiredDevice string, allSimIDsGroupedBySimVersion SimulatorsGroupedByIOSVersionsModel) (string, error) { | |
latestOsVersion := "" | |
for osVersion, simInfos := range allSimIDsGroupedBySimVersion { | |
if !strings.HasPrefix(osVersion, desiredPlatform) { | |
continue | |
} | |
deviceExist := false | |
for _, simInfo := range simInfos { | |
if simInfo.Name == desiredDevice { | |
deviceExist = true | |
break | |
} | |
} | |
if !deviceExist { | |
continue | |
} | |
if latestOsVersion == "" { | |
latestOsVersion = osVersion | |
} else { | |
greater, err := isOsVersionGreater(osVersion, latestOsVersion) | |
if err != nil { | |
return "", err | |
} | |
if greater { | |
latestOsVersion = osVersion | |
} | |
} | |
} | |
if latestOsVersion == "" { | |
return "", fmt.Errorf("failed to find latest os version for (%s) - (%s)", desiredPlatform, desiredDevice) | |
} | |
return latestOsVersion, nil | |
} | |
//======================================= | |
// Main | |
//======================================= | |
// GetSimulator ... | |
func GetSimulator(simulatorPlatform, simulatorDevice, simulatorOsVersion string) (SimInfoModel, error) { | |
cmd := exec.Command("xcrun", "simctl", "list") | |
outBytes, err := cmd.CombinedOutput() | |
if err != nil { | |
return SimInfoModel{}, err | |
} | |
simctlListOut := string(outBytes) | |
allSimIDsGroupedBySimVersion := collectAllSimIDs(simctlListOut) | |
// | |
// map desired inputs | |
simulatorPlatformSplit := strings.Split(simulatorPlatform, " Simulator") | |
if len(simulatorPlatformSplit) == 0 { | |
return SimInfoModel{}, fmt.Errorf("failed to parse simulator platform (%s)", simulatorPlatform) | |
} | |
if simulatorDevice == "iPad" { | |
fmt.Printf("Given device (%s) is deprecated, using (iPad 2)...", simulatorDevice) | |
simulatorDevice = "iPad 2" | |
} | |
desiredPlatform := simulatorPlatformSplit[0] | |
desiredOsVersion := "" | |
if simulatorOsVersion == "latest" { | |
latestOsVersion, err := getLatestOsVersion(desiredPlatform, simulatorDevice, allSimIDsGroupedBySimVersion) | |
if err != nil { | |
return SimInfoModel{}, fmt.Errorf("failed to get latest os version, error: %s", err) | |
} | |
desiredOsVersion = latestOsVersion | |
} else { | |
normalizedOsVersion := simulatorOsVersion | |
osVersionSplit := strings.Split(normalizedOsVersion, ".") | |
if len(osVersionSplit) > 2 { | |
normalizedOsVersion = strings.Join(osVersionSplit[0:2], ".") | |
} | |
desiredOsVersion = fmt.Sprintf("%s %s", desiredPlatform, normalizedOsVersion) | |
} | |
// | |
// find desired simulator | |
simInfoList, found := allSimIDsGroupedBySimVersion[desiredOsVersion] | |
if !found { | |
return SimInfoModel{}, fmt.Errorf("no simulator found for desired os: %s", desiredOsVersion) | |
} | |
for _, simInfo := range simInfoList { | |
if simInfo.Name == simulatorDevice { | |
return simInfo, nil | |
} | |
} | |
return SimInfoModel{}, fmt.Errorf("%s - %s - %s not found", simulatorPlatform, simulatorDevice, simulatorOsVersion) | |
} | |
func getXcodeDeveloperDirPath() (string, error) { | |
cmd := exec.Command("xcode-select", "--print-path") | |
outBytes, err := cmd.CombinedOutput() | |
outStr := string(outBytes) | |
return strings.TrimSpace(outStr), err | |
} | |
// BootSimulator ... | |
func BootSimulator(simulator SimInfoModel, xcodebuildVersion XcodebuildVersionModel) error { | |
simulatorApp := "Simulator" | |
if xcodebuildVersion.MajorVersion == 6 { | |
simulatorApp = "iOS Simulator" | |
} | |
xcodeDevDirPth, err := getXcodeDeveloperDirPath() | |
if err != nil { | |
return fmt.Errorf("Failed to get Xcode Developer Directory - most likely Xcode.app is not installed") | |
} | |
simulatorAppFullPath := filepath.Join(xcodeDevDirPth, "Applications", simulatorApp+".app") | |
cmd := command.New("open", simulatorAppFullPath, "--args", "-CurrentDeviceUDID", simulator.SimID) | |
fmt.Printf("$ %s\n", cmd.PrintableCommandArgs()) | |
out, err := cmd.RunAndReturnTrimmedCombinedOutput() | |
outStr := string(out) | |
if err != nil { | |
return fmt.Errorf("failed to start simulators (%s), output: %s, error: %s", simulator.SimID, outStr, err) | |
} | |
return nil | |
} | |
func main() { | |
platfrom := "iOS Simulator" | |
device := "iPhone 6s Plus" | |
version := "11.2" | |
sim, err := GetSimulator(platfrom, device, version) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("Sim: %v\n", sim) | |
xcodeVersion, err := GetXcodeVersion() | |
if err != nil { | |
panic(err) | |
} | |
if err := BootSimulator(sim, xcodeVersion); err != nil { | |
panic(err) | |
} | |
booted := sim.Status == "Booted" | |
for !booted { | |
sim, err := GetSimulator(platfrom, device, version) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("Sim: %v\n", sim) | |
booted = sim.Status == "Booted" | |
time.Sleep(10 * time.Second) | |
} | |
cmd := command.New("envman", "add", "--key", "SIM_UUID", "--value", sim.SimID) | |
if err := cmd.Run(); err != nil { | |
panic(err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment