func (configs ConfigsModel) print() { log.Info("Configs:") log.Detail("- GradleFile: %s", configs.GradleFile) log.Detail("- UnitTestTasks: %s", configs.UnitTestTasks) log.Detail("- GradlewPath: %s", configs.GradlewPath) log.Detail("- UnitTestFlags: %s", configs.UnitTestFlags) log.Detail("- DeployDir: %s", configs.DeployDir) }
func (configs ConfigsModel) print() { log.Info("Configs:") log.Detail("- CarthageCommand: %s", configs.CarthageCommand) log.Detail("- CarthageOptions: %s", configs.CarthageOptions) log.Detail("- GithubAccessToken: %s", configs.GithubAccessToken) fmt.Println() }
func (configs ConfigsModel) print() { log.Info("Build Configs:") log.Detail("- XamarinSolution: %s", configs.XamarinSolution) log.Detail("- XamarinConfiguration: %s", configs.XamarinConfiguration) log.Detail("- XamarinPlatform: %s", configs.XamarinPlatform) log.Info("Nunit Configs:") log.Detail("- CustomOptions: %s", configs.CustomOptions) log.Info("Other Configs:") log.Detail("- DeployDir: %s", configs.DeployDir) }
func runXcodeBuildCmd(useStdOut bool, args ...string) (string, int, error) { // command buildCmd := cmd.CreateXcodebuildCmd(args...) // output buffer var outBuffer bytes.Buffer // additional output writers, like StdOut outWritters := []io.Writer{} if useStdOut { outWritters = append(outWritters, os.Stdout) } // unify as a single writer outWritter := cmd.CreateBufferedWriter(&outBuffer, outWritters...) // and set the writer buildCmd.Stdin = nil buildCmd.Stdout = outWritter buildCmd.Stderr = outWritter buildCmd.Env = append(os.Environ(), xcodeCommandEnvs...) cmdArgsForPrint := cmd.PrintableCommandArgsWithEnvs(buildCmd.Args, xcodeCommandEnvs) log.Detail("$ %s", cmdArgsForPrint) err := buildCmd.Run() if err != nil { if exitError, ok := err.(*exec.ExitError); ok { waitStatus, ok := exitError.Sys().(syscall.WaitStatus) if !ok { return outBuffer.String(), 1, errors.New("Failed to cast exit status") } return outBuffer.String(), waitStatus.ExitStatus(), err } return outBuffer.String(), 1, err } return outBuffer.String(), 0, nil }
func (configs ConfigsModel) print() { log.Info("Configs:") log.Detail("- JSONKeyPath: %s", secureInput(configs.JSONKeyPath)) log.Detail("- PackageName: %s", configs.PackageName) log.Detail("- ApkPath: %s", configs.ApkPath) log.Detail("- Track: %s", configs.Track) log.Detail("- UserFraction: %s", configs.UserFraction) log.Detail("- WhatsnewsDir: %s", configs.WhatsnewsDir) log.Info("Deprecated Configs:") log.Detail("- ServiceAccountEmail: %s", secureInput(configs.ServiceAccountEmail)) log.Detail("- P12KeyPath: %s", secureInput(configs.P12KeyPath)) }
func (configs ConfigsModel) print() { fmt.Println() log.Info("Configs:") log.Detail(" - CertificateURL: %s", secureInput(configs.CertificateURL)) log.Detail(" - CertificatePassphrase: %s", secureInput(configs.CertificatePassphrase)) log.Detail(" - ProvisioningProfileURL: %s", secureInput(configs.ProvisioningProfileURL)) log.Detail(" - DefaultCertificateURL: %s", secureInput(configs.DefaultCertificateURL)) log.Detail(" - DefaultCertificatePassphrase: %s", secureInput(configs.DefaultCertificatePassphrase)) log.Detail(" - DefaultProvisioningProfileURL: %s", secureInput(configs.DefaultProvisioningProfileURL)) log.Detail(" - KeychainPath: %s", configs.KeychainPath) log.Detail(" - KeychainPassword: %s", secureInput(configs.KeychainPassword)) }
func (configs ConfigsModel) print() { log.Info("Build Configs:") log.Detail("- XamarinSolution: %s", configs.XamarinSolution) log.Detail("- XamarinConfiguration: %s", configs.XamarinConfiguration) log.Detail("- XamarinPlatform: %s", configs.XamarinPlatform) log.Info("Xamarin UITest Configs:") log.Detail("- TestToRun: %s", configs.TestToRun) log.Detail("- SimulatorDevice: %s", configs.SimulatorDevice) log.Detail("- SimulatorOsVersion: %s", configs.SimulatorOsVersion) log.Info("Other Configs:") log.Detail("- DeployDir: %s", configs.DeployDir) }
// BootSimulator ... func BootSimulator(simulator models.SimInfoModel, xcodebuildVersion models.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") openCmd := exec.Command("open", simulatorAppFullPath, "--args", "-CurrentDeviceUDID", simulator.SimID) log.Detail("$ %s", cmd.PrintableCommandArgs(openCmd.Args)) out, err := openCmd.CombinedOutput() 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 runPrettyXcodeBuildCmd(useStdOut bool, xcprettyArgs []string, xcodebuildArgs []string) (string, int, error) { // buildCmd := cmd.CreateXcodebuildCmd(xcodebuildArgs...) prettyCmd := cmd.CreateXcprettyCmd(xcprettyArgs...) // var buildOutBuffer bytes.Buffer // pipeReader, pipeWriter := io.Pipe() // // build outputs: // - write it into a buffer // - write it into the pipe, which will be fed into xcpretty buildOutWriters := []io.Writer{pipeWriter} buildOutWriter := cmd.CreateBufferedWriter(&buildOutBuffer, buildOutWriters...) // var prettyOutWriter io.Writer if useStdOut { prettyOutWriter = os.Stdout } // and set the writers buildCmd.Stdin = nil buildCmd.Stdout = buildOutWriter buildCmd.Stderr = buildOutWriter // prettyCmd.Stdin = pipeReader prettyCmd.Stdout = prettyOutWriter prettyCmd.Stderr = prettyOutWriter // buildCmd.Env = append(os.Environ(), xcodeCommandEnvs...) log.Detail("$ set -o pipefail && %s | %v", cmd.PrintableCommandArgsWithEnvs(buildCmd.Args, xcodeCommandEnvs), cmd.PrintableCommandArgs(prettyCmd.Args)) fmt.Println() if err := buildCmd.Start(); err != nil { return buildOutBuffer.String(), 1, err } if err := prettyCmd.Start(); err != nil { return buildOutBuffer.String(), 1, err } defer func() { if err := pipeWriter.Close(); err != nil { log.Warn("Failed to close xcodebuild-xcpretty pipe, error: %s", err) } if err := prettyCmd.Wait(); err != nil { log.Warn("xcpretty command failed, error: %s", err) } }() if err := buildCmd.Wait(); err != nil { if exitError, ok := err.(*exec.ExitError); ok { waitStatus, ok := exitError.Sys().(syscall.WaitStatus) if !ok { return buildOutBuffer.String(), 1, errors.New("Failed to cast exit status") } return buildOutBuffer.String(), waitStatus.ExitStatus(), err } return buildOutBuffer.String(), 1, err } return buildOutBuffer.String(), 0, nil }
func main() { configs := createConfigsModelFromEnvs() fmt.Println() configs.print() if err := configs.validate(); err != nil { log.Error("Issue with input: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } // Get Simulator Infos fmt.Println() log.Info("Collecting simulator info...") simulatorInfo, err := getSimulatorInfo(configs.SimulatorOsVersion, configs.SimulatorDevice) if err != nil { log.Error("Failed to get simulator infos, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } log.Done("Simulator (%s), id: (%s), status: %s", simulatorInfo.Name, simulatorInfo.ID, simulatorInfo.Status) // --- // Nunit Console path nunitConsolePth, err := nunit.SystemNunit3ConsolePath() if err != nil { log.Error("Failed to get system insatlled nunit3-console.exe path, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } // --- // // build fmt.Println() log.Info("Building all iOS Xamarin UITest and Referred Projects in solution: %s", configs.XamarinSolution) builder, err := builder.New(configs.XamarinSolution, []constants.ProjectType{constants.ProjectTypeIOS}, false) if err != nil { log.Error("Failed to create xamarin builder, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } callback := func(solutionName string, projectName string, projectType constants.ProjectType, commandStr string, alreadyPerformed bool) { fmt.Println() if projectType == constants.ProjectTypeXamarinUITest { log.Info("Building test project: %s", projectName) } else { log.Info("Building project: %s", projectName) } log.Done("$ %s", commandStr) if alreadyPerformed { log.Warn("build command already performed, skipping...") } fmt.Println() } warnings, err := builder.BuildAllXamarinUITestAndReferredProjects(configs.XamarinConfiguration, configs.XamarinPlatform, nil, callback) for _, warning := range warnings { log.Warn(warning) } if err != nil { log.Error("Build failed, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } projectOutputMap, err := builder.CollectProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform) if err != nil { log.Error("Failed to collect project outputs, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } testProjectOutputMap, warnings, err := builder.CollectXamarinUITestProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform) for _, warning := range warnings { log.Warn(warning) } if err != nil { log.Error("Failed to collect test project output, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } // --- // // Run nunit tests nunitConsole, err := nunit.New(nunitConsolePth) if err != nil { log.Error("Failed to create nunit console model, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } resultLogPth := filepath.Join(configs.DeployDir, "TestResult.xml") nunitConsole.SetResultLogPth(resultLogPth) // Artifacts resultLog := "" for testProjectName, testProjectOutput := range testProjectOutputMap { if len(testProjectOutput.ReferredProjectNames) == 0 { log.Warn("Test project (%s) does not refers to any project, skipping...", testProjectName) continue } for _, projectName := range testProjectOutput.ReferredProjectNames { projectOutput, ok := projectOutputMap[projectName] if !ok { continue } appPth := "" for _, output := range projectOutput.Outputs { if output.OutputType == constants.OutputTypeAPP { appPth = output.Pth } } if appPth == "" { log.Error("No app generated for project: %s", projectName) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } // Set APP_BUNDLE_PATH env to let the test know which .app file should be tested // This env is used in the Xamarin.UITest project to refer to the .app path if err := os.Setenv("APP_BUNDLE_PATH", appPth); err != nil { log.Error("Failed to set APP_BUNDLE_PATH environment, without this env test will fail, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } // Run test fmt.Println() log.Info("Testing (%s) against (%s)", testProjectName, projectName) log.Detail("test dll: %s", testProjectOutput.Output.Pth) log.Detail("app: %s", appPth) nunitConsole.SetDLLPth(testProjectOutput.Output.Pth) nunitConsole.SetTestToRun(configs.TestToRun) fmt.Println() log.Info("Running Xamarin UITest") log.Done("$ %s", nunitConsole.PrintableCommand()) fmt.Println() err := nunitConsole.Run() testLog, readErr := testResultLogContent(resultLogPth) if readErr != nil { log.Warn("Failed to read test result, error: %s", readErr) } resultLog = testLog if err != nil { log.Error("Test failed, error: %s", err) if errorMsg, err := parseErrorFromResultLog(resultLog); err != nil { log.Warn("Failed to parse error message from result log, error: %s", err) } else if errorMsg != "" { log.Error("%s", errorMsg) } if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } if resultLog != "" { if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err) } } os.Exit(1) } } } if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "succeeded"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } if resultLog != "" { if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err) } } }
func main() { configs := createConfigsModelFromEnvs() fmt.Println() configs.print() if err := configs.validate(); err != nil { fmt.Println() log.Error("Issue with input: %s", err) os.Exit(1) } // // Create client fmt.Println() log.Info("Authenticateing") jwtConfig := new(jwt.Config) if configs.JSONKeyPath != "" { jsonKeyPth := "" if strings.HasPrefix(configs.JSONKeyPath, "file://") { jsonKeyPth = strings.TrimPrefix(configs.JSONKeyPath, "file://") } else { tmpDir, err := pathutil.NormalizedOSTempDirPath("__google-play-deploy__") if err != nil { log.Error("Failed to create tmp dir, error: %s", err) os.Exit(1) } jsonKeyPth = filepath.Join(tmpDir, "key.json") if err := downloadFile(configs.JSONKeyPath, jsonKeyPth); err != nil { log.Error("Failed to download json key file, error: %s", err) os.Exit(1) } } authConfig, err := jwtConfigFromJSONKeyFile(jsonKeyPth) if err != nil { log.Error("Failed to create auth config from json key file, error: %s", err) os.Exit(1) } jwtConfig = authConfig } else { p12KeyPath := "" if strings.HasPrefix(configs.P12KeyPath, "file://") { p12KeyPath = strings.TrimPrefix(configs.P12KeyPath, "file://") } else { tmpDir, err := pathutil.NormalizedOSTempDirPath("__google-play-deploy__") if err != nil { log.Error("Failed to create tmp dir, error: %s", err) os.Exit(1) } p12KeyPath = filepath.Join(tmpDir, "key.p12") if err := downloadFile(configs.P12KeyPath, p12KeyPath); err != nil { log.Error("Failed to download p12 key file, error: %s", err) os.Exit(1) } } authConfig, err := jwtConfigFromP12KeyFile(p12KeyPath, configs.ServiceAccountEmail) if err != nil { log.Error("Failed to create auth config from p12 key file, error: %s", err) os.Exit(1) } jwtConfig = authConfig } client := jwtConfig.Client(oauth2.NoContext) service, err := androidpublisher.New(client) if err != nil { log.Error("Failed to create publisher service, error: %s", err) os.Exit(1) } log.Done("Authenticated client created") // --- // // Create insert edit fmt.Println() log.Info("Create new edit") editsService := androidpublisher.NewEditsService(service) editsInsertCall := editsService.Insert(configs.PackageName, nil) appEdit, err := editsInsertCall.Do() if err != nil { log.Error("Failed to perform edit insert call, error: %s", err) os.Exit(1) } log.Detail(" editID: %s", appEdit.Id) // --- // // Upload APKs fmt.Println() log.Info("Upload apks") versionCodes := []int64{} apkPaths := strings.Split(configs.ApkPath, "|") for _, apkPath := range apkPaths { apkFile, err := os.Open(apkPath) if err != nil { log.Error("Failed to read apk (%s), error: %s", apkPath, err) os.Exit(1) } editsApksService := androidpublisher.NewEditsApksService(service) editsApksUloadCall := editsApksService.Upload(configs.PackageName, appEdit.Id) editsApksUloadCall.Media(apkFile, googleapi.ContentType("application/vnd.android.package-archive")) apk, err := editsApksUloadCall.Do() if err != nil { log.Error("Failed to upload apk, error: %s", err) os.Exit(1) } log.Detail(" uploaded apk version: %d", apk.VersionCode) versionCodes = append(versionCodes, apk.VersionCode) } // --- // // Update track fmt.Println() log.Info("Update track") editsTracksService := androidpublisher.NewEditsTracksService(service) newTrack := androidpublisher.Track{ Track: configs.Track, VersionCodes: versionCodes, } if configs.Track == "rollout" { userFraction, err := strconv.ParseFloat(configs.UserFraction, 64) if err != nil { log.Error("Failed to parse user fraction, error: %s", err) os.Exit(1) } newTrack.UserFraction = userFraction } editsTracksUpdateCall := editsTracksService.Update(configs.PackageName, appEdit.Id, configs.Track, &newTrack) track, err := editsTracksUpdateCall.Do() if err != nil { log.Error("Failed to update track, error: %s", err) os.Exit(1) } log.Detail(" updated track: %s", track.Track) log.Detail(" assigned apk versions: %v", track.VersionCodes) // --- // // Update listing if configs.WhatsnewsDir != "" { fmt.Println() log.Info("Update listing") recentChangesMap, err := readLocalisedRecentChanges(configs.WhatsnewsDir) if err != nil { log.Error("Failed to read whatsnews, error: %s", err) os.Exit(1) } editsApklistingsService := androidpublisher.NewEditsApklistingsService(service) for _, versionCode := range versionCodes { log.Detail(" updating recent changes for version: %d", versionCode) for language, recentChanges := range recentChangesMap { newApkListing := androidpublisher.ApkListing{ Language: language, RecentChanges: recentChanges, } editsApkListingsCall := editsApklistingsService.Update(configs.PackageName, appEdit.Id, versionCode, language, &newApkListing) apkListing, err := editsApkListingsCall.Do() if err != nil { log.Error("Failed to update listing, error: %s", err) os.Exit(1) } log.Detail(" - language: %s", apkListing.Language) } } } // --- // // Validate edit fmt.Println() log.Info("Validating edit") editsValidateCall := editsService.Validate(configs.PackageName, appEdit.Id) if _, err := editsValidateCall.Do(); err != nil { log.Error("Failed to validate edit, error: %s", err) os.Exit(1) } log.Done("Edit is valid") // --- // // Commit edit fmt.Println() log.Info("Committing edit") editsCommitCall := editsService.Commit(configs.PackageName, appEdit.Id) if _, err := editsCommitCall.Do(); err != nil { log.Error("Failed to commit edit, error: %s", err) os.Exit(1) } log.Done("Edit committed") // --- }
func (configs ConfigsModel) print() { log.Info("Build Configs:") log.Detail("- XamarinSolution: %s", configs.XamarinSolution) log.Detail("- XamarinConfiguration: %s", configs.XamarinConfiguration) log.Detail("- XamarinPlatform: %s", configs.XamarinPlatform) log.Info("Xamarin Test Cloud Configs:") log.Detail("- User: %s", configs.User) log.Detail("- APIKey: %s", configs.APIKey) log.Detail("- Devices: %s", configs.Devices) log.Detail("- IsAsync: %s", configs.IsAsync) log.Detail("- Series: %s", configs.Series) log.Detail("- Parallelization: %s", configs.Parallelization) log.Detail("- CustomOptions: %s", configs.CustomOptions) log.Info("Other Configs:") log.Detail("- DeployDir: %s", configs.DeployDir) }
func (configs ConfigsModel) print() { fmt.Println() log.Info("Project Parameters:") log.Detail("- ProjectPath: %s", configs.ProjectPath) log.Detail("- Scheme: %s", configs.Scheme) fmt.Println() log.Info("Simulator Configs:") log.Detail("- SimulatorPlatform: %s", configs.SimulatorPlatform) log.Detail("- SimulatorDevice: %s", configs.SimulatorDevice) log.Detail("- SimulatorOsVersion: %s", configs.SimulatorOsVersion) fmt.Println() log.Info("Test Run Configs:") log.Detail("- OutputTool: %s", configs.OutputTool) log.Detail("- IsCleanBuild: %s", configs.IsCleanBuild) log.Detail("- IsSingleBuild: %s", configs.IsSingleBuild) log.Detail("- ShouldBuildBeforeTest: %s", configs.ShouldBuildBeforeTest) log.Detail("- ShouldRetryTestOnFail: %s", configs.ShouldRetryTestOnFail) log.Detail("- GenerateCodeCoverageFiles: %s", configs.GenerateCodeCoverageFiles) log.Detail("- ExportUITestArtifacts: %s", configs.ExportUITestArtifacts) log.Detail("- TestOptions: %s", configs.TestOptions) log.Detail("- XcprettyTestOptions: %s", configs.XcprettyTestOptions) log.Detail("- WaitForSimulatorBoot: %s", configs.WaitForSimulatorBoot) }
func main() { configs := createConfigsModelFromEnvs() fmt.Println() configs.print() if err := configs.validate(); err != nil { fail("Issue with input: %s", err) } customOptions := []string{} if configs.CarthageOptions != "" { options, err := shellquote.Split(configs.CarthageOptions) if err != nil { fail("Failed to shell split CarthageOptions (%s), error: %s", configs.CarthageOptions) } customOptions = options } // // Exit if bootstrap is cached log.Info("Check if cache is available") hasCachedItems, err := isCacheAvailable(configs.SourceDir) if err != nil { fail("Failed to check cached files, error: %s", err) } log.Detail("has cahched items: %v", hasCachedItems) if configs.CarthageCommand == "bootstrap" && hasCachedItems { log.Done("Using cached dependencies for bootstrap command. If you would like to force update your dependencies, select `update` as CarthageCommand and re-run your build.") os.Exit(0) } fmt.Println() // --- // // Run carthage command log.Info("Running Carthage command") args := append([]string{configs.CarthageCommand}, customOptions...) cmd := cmdex.NewCommand("carthage", args...) if configs.GithubAccessToken != "" { log.Detail("Appending GITHUB_ACCESS_TOKEN to process environments") cmd.AppendEnvs([]string{fmt.Sprintf("GITHUB_ACCESS_TOKEN=%s", configs.GithubAccessToken)}) } cmd.SetStdout(os.Stdout) cmd.SetStderr(os.Stderr) log.Done("$ %s", cmdex.PrintableCommandArgs(false, cmd.GetCmd().Args)) fmt.Println() if err := cmd.Run(); err != nil { fail("Carthage command failed, error: %s", err) } // --- // // Create cache if configs.CarthageCommand == "bootstrap" { fmt.Println() log.Info("Creating cache") cacheFilePth := filepath.Join(configs.SourceDir, carthageDirName, cacheFileName) swiftVersion, err := swiftVersion() if err != nil { fail("Failed to get swift version, error: %s", err) } resolvedFilePath := filepath.Join(configs.SourceDir, resolvedFileName) resolved, err := contentsOfCartfileResolved(resolvedFilePath) if err != nil { fail("Failed to get resolved file content, error: %s", err) } cacheContent := fmt.Sprintf("--Swift version: %s --Swift version \n --%s: %s --%s", swiftVersion, resolvedFileName, resolved, resolvedFileName) carthageDir := filepath.Join(configs.SourceDir, carthageDirName) if exist, err := pathutil.IsPathExists(carthageDir); err != nil { fail("Failed to check if dir exists at (%s), error: %s", carthageDir, err) } else if !exist { if err := os.Mkdir(carthageDir, 0777); err != nil { fail("Failed to create dir (%s), error: %s", carthageDir, err) } } if err := fileutil.WriteStringToFile(cacheFilePth, cacheContent); err != nil { fail("Failed to write cahe file, error: %s", err) } log.Done("Cachefile: %s", cacheFilePth) } // --- }
func main() { configs := createConfigsModelFromEnvs() fmt.Println() configs.print() if err := configs.validate(); err != nil { registerFail("Issue with input: %s", err) } // // Determining calabash-android version fmt.Println() log.Info("Determining calabash-android version...") rubyCommand, err := rubycmd.NewRubyCommandModel() if err != nil { registerFail("Failed to create ruby command, err: %s", err) } calabashAndroidVersion := "" useBundler := false if configs.GemFilePath != "" { if exist, err := pathutil.IsPathExists(configs.GemFilePath); err != nil { registerFail("Failed to check if Gemfile exists at (%s) exist, error: %s", configs.GemFilePath, err) } else if exist { log.Detail("Gemfile exists at: %s", configs.GemFilePath) gemfileDir := filepath.Dir(configs.GemFilePath) gemfileLockPth := filepath.Join(gemfileDir, "Gemfile.lock") if exist, err := pathutil.IsPathExists(gemfileLockPth); err != nil { registerFail("Failed to check if Gemfile.lock exists at (%s), error: %s", gemfileLockPth, err) } else if exist { log.Detail("Gemfile.lock exists at: %s", gemfileLockPth) version, err := calabashAndroidVersionFromGemfileLock(gemfileLockPth) if err != nil { registerFail("Failed to get calabash-android version from Gemfile.lock, error: %s", err) } log.Detail("calabash-android version in Gemfile.lock: %s", version) calabashAndroidVersion = version useBundler = true } else { log.Warn("Gemfile.lock doest no find with calabash-android gem at: %s", gemfileLockPth) } } else { log.Warn("Gemfile doest no find with calabash-android gem at: %s", configs.GemFilePath) } } if configs.CalabashAndroidVersion != "" { log.Detail("calabash-android version in configs: %s", configs.CalabashAndroidVersion) calabashAndroidVersion = configs.CalabashAndroidVersion useBundler = false } if calabashAndroidVersion == "" { log.Done("using calabash-android latest version") } else { log.Done("using calabash-android version: %s", calabashAndroidVersion) } // --- // // Intsalling calabash-android gem fmt.Println() log.Info("Installing calabash-android gem...") calabashAndroidArgs := []string{} // If Gemfile given with calabash-android and calabash_android_version input does not override calabash-android version // Run `bundle install` // Run calabash-android with `bundle exec` if useBundler { bundleInstallArgs := []string{"bundle", "install", "--jobs", "20", "--retry", "5"} // bundle install bundleInstallCmd, err := rubyCommand.Command(false, bundleInstallArgs) if err != nil { registerFail("Failed to create command, error: %s", err) } bundleInstallCmd.AppendEnvs([]string{"BUNDLE_GEMFILE=" + configs.GemFilePath}) log.Detail("$ %s", cmdex.PrintableCommandArgs(false, bundleInstallArgs)) if err := bundleInstallCmd.Run(); err != nil { registerFail("bundle install failed, error: %s", err) } // --- calabashAndroidArgs = []string{"bundle", "exec"} } calabashAndroidArgs = append(calabashAndroidArgs, "calabash-android") // If no need to use bundler if !useBundler { if calabashAndroidVersion != "" { // ... and calabash-android version detected // Install calabash-android detetcted version with `gem install` // Append version param to calabash-android command installed, err := rubyCommand.IsGemInstalled("calabash-android", calabashAndroidVersion) if err != nil { registerFail("Failed to check if calabash-android (v%s) installed, error: %s", calabashAndroidVersion, err) } if !installed { installCommands, err := rubyCommand.GemInstallCommands("calabash-android", calabashAndroidVersion) if err != nil { registerFail("Failed to create gem install commands, error: %s", err) } for _, installCommand := range installCommands { log.Detail("$ %s", cmdex.PrintableCommandArgs(false, installCommand.GetCmd().Args)) if err := installCommand.Run(); err != nil { registerFail("command failed, error: %s", err) } } } else { log.Detail("calabash-android %s installed", calabashAndroidVersion) } } else { // ... and using latest version of calabash-android // Install calabash-android latest version with `gem install` installCommands, err := rubyCommand.GemInstallCommands("calabash-android", "") if err != nil { registerFail("Failed to create gem install commands, error: %s", err) } for _, installCommand := range installCommands { log.Detail("$ %s", cmdex.PrintableCommandArgs(false, installCommand.GetCmd().Args)) if err := installCommand.Run(); err != nil { registerFail("command failed, error: %s", err) } } } } // --- // // Search for debug.keystore fmt.Println() log.Info("Search for debug.keystore...") debugKeystorePth := "" homeDir := pathutil.UserHomeDir() // $HOME/.android/debug.keystore androidDebugKeystorePth := filepath.Join(homeDir, ".android", "debug.keystore") debugKeystorePth = androidDebugKeystorePth if exist, err := pathutil.IsPathExists(androidDebugKeystorePth); err != nil { registerFail("Failed to check if debug.keystore exists at (%s), error: %s", androidDebugKeystorePth, err) } else if !exist { log.Warn("android debug keystore not exist at: %s", androidDebugKeystorePth) // $HOME/.local/share/Mono for Android/debug.keystore xamarinDebugKeystorePth := filepath.Join(homeDir, ".local", "share", "Mono for Android", "debug.keystore") log.Detail("checking xamarin debug keystore at: %s", xamarinDebugKeystorePth) if exist, err := pathutil.IsPathExists(xamarinDebugKeystorePth); err != nil { registerFail("Failed to check if debug.keystore exists at (%s), error: %s", xamarinDebugKeystorePth, err) } else if !exist { log.Warn("xamarin debug keystore not exist at: %s", xamarinDebugKeystorePth) log.Detail("generating debug keystore") // `keytool -genkey -v -keystore "#{debug_keystore}" -alias androiddebugkey -storepass android -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US"` keytoolArgs := []string{"keytool", "-genkey", "-v", "-keystore", debugKeystorePth, "-alias", "androiddebugkey", "-storepass", "android", "-keypass", "android", "-keyalg", "RSA", "-keysize", "2048", "-validity", "10000", "-dname", "CN=Android Debug,O=Android,C=US"} cmd, err := cmdex.NewCommandFromSlice(keytoolArgs) if err != nil { registerFail("Failed to create command, error: %s", err) } log.Detail("$ %s", cmdex.PrintableCommandArgs(false, keytoolArgs)) if err := cmd.Run(); err != nil { registerFail("Failed to generate debug.keystore, error: %s", err) } log.Detail("using debug keystore: %s", debugKeystorePth) } else { log.Detail("using xamarin debug keystore: %s", xamarinDebugKeystorePth) debugKeystorePth = xamarinDebugKeystorePth } } else { log.Detail("using android debug keystore: %s", androidDebugKeystorePth) } // --- // // Resign apk with debug.keystore fmt.Println() log.Info("Resign apk with debug.keystore...") resignArgs := []string{"calabash-android", "resign", configs.ApkPath} resignCmd, err := rubyCommand.Command(useBundler, resignArgs) if err != nil { registerFail("Failed to create command, error: %s", err) } log.Detail("$ %s", cmdex.PrintableCommandArgs(false, resignArgs)) fmt.Println() resignCmd.SetStdout(os.Stdout) resignCmd.SetStderr(os.Stderr) if err := resignCmd.Run(); err != nil { registerFail("Failed to run command, error: %s", err) } // --- // // Run calabash-android fmt.Println() log.Info("Running calabash-android test...") testArgs := []string{"calabash-android", "run", configs.ApkPath} testCmd, err := rubyCommand.Command(useBundler, testArgs) if err != nil { registerFail("Failed to create command, error: %s", err) } log.Detail("$ %s", cmdex.PrintableCommandArgs(false, testArgs)) fmt.Println() testCmd.SetStdout(os.Stdout) testCmd.SetStderr(os.Stderr) if err := testCmd.Run(); err != nil { registerFail("Failed to run command, error: %s", err) } // --- if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "succeeded"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } }
func main() { configs := createConfigsModelFromEnvs() configs.print() if err := configs.validate(); err != nil { log.Error("Issue with input: %s", err) os.Exit(1) } fmt.Println() // Validate Certificates certificateURLPassphraseMap := map[string]string{} if configs.CertificateURL != "" { certificateURLs := strings.Split(configs.CertificateURL, "|") certificatePassphrases := strings.Split(configs.CertificatePassphrase, "|") if len(certificateURLs) != len(certificatePassphrases) { log.Error("Certificate url count: (%d), not equals to Certificate Passphrase count: (%d)", len(certificateURLs), len(certificatePassphrases)) os.Exit(1) } for i := 0; i < len(certificateURLs); i++ { certificateURL := certificateURLs[i] certificatePassphrase := certificatePassphrases[i] certificateURLPassphraseMap[certificateURL] = certificatePassphrase } } if configs.DefaultCertificateURL != "" { log.Detail("Default Certificate given") certificateURLPassphraseMap[configs.DefaultCertificateURL] = configs.DefaultCertificatePassphrase } certificateCount := len(certificateURLPassphraseMap) log.Detail("Provided Certificate count: %d", certificateCount) if certificateCount == 0 { log.Error("No Certificate provided") os.Exit(1) } // Validate Provisioning Profiles provisioningProfileURLs := strings.Split(configs.ProvisioningProfileURL, "|") if configs.DefaultProvisioningProfileURL != "" { log.Detail("Default Provisioning Profile given") provisioningProfileURLs = append(provisioningProfileURLs, configs.DefaultProvisioningProfileURL) } profileCount := len(provisioningProfileURLs) log.Detail("Provided Provisioning Profile count: %d", profileCount) if profileCount == 0 { log.Error("No Provisioning Profile provided") os.Exit(1) } // // Init homeDir := os.Getenv("HOME") provisioningProfileDir := path.Join(homeDir, "Library/MobileDevice/Provisioning Profiles") if exist, err := pathutil.IsPathExists(provisioningProfileDir); err != nil { log.Error("Failed to check path (%s), err: %s", provisioningProfileDir, err) os.Exit(1) } else if !exist { if err := os.MkdirAll(provisioningProfileDir, 0777); err != nil { log.Error("Failed to create path (%s), err: %s", provisioningProfileDir, err) os.Exit(1) } } tempDir, err := pathutil.NormalizedOSTempDirPath("bitrise-cert-tmp") if err != nil { log.Error("Failed to create tmp directory, err: %s", err) os.Exit(1) } if exist, err := pathutil.IsPathExists(configs.KeychainPath); err != nil { log.Error("Failed to check path (%s), err: %s", configs.KeychainPath, err) os.Exit(1) } else if !exist { fmt.Println() log.Warn("Keychain (%s) does not exist", configs.KeychainPath) keychainPth := fmt.Sprintf("%s-db", configs.KeychainPath) log.Detail(" Checking (%s)", keychainPth) if exist, err := pathutil.IsPathExists(keychainPth); err != nil { log.Error("Failed to check path (%s), err: %s", keychainPth, err) os.Exit(1) } else if !exist { log.Info("Creating keychain: %s", configs.KeychainPath) if out, err := runCommandAndReturnCombinedStdoutAndStderr("security", "-v", "create-keychain", "-p", configs.KeychainPassword, configs.KeychainPath); err != nil { log.Error("Failed to create keychain, output: %s", out) log.Error("Failed to create keychain, err: %s", err) os.Exit(1) } } } else { log.Detail("Keychain already exists, using it: %s", configs.KeychainPath) } // // Download certificate fmt.Println() log.Info("Downloading & installing Certificate(s)") certificatePassphraseMap := map[string]string{} idx := 0 for certURL, pass := range certificateURLPassphraseMap { fmt.Println() log.Detail("=> Downloading certificate: %d/%d", idx+1, certificateCount) certPath := path.Join(tempDir, fmt.Sprintf("Certificate-%d.p12", idx)) if err := downloadFile(certPath, certURL); err != nil { log.Error("Download failed, err: %s", err) os.Exit(1) } certificatePassphraseMap[certPath] = pass idx++ } // // Install certificate fmt.Println() log.Detail("=> Installing downloaded certificate") for cert, pass := range certificatePassphraseMap { // Import items into a keychain. importOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "import", cert, "-k", configs.KeychainPath, "-P", pass, "-A") if err != nil { log.Error("Command failed, output: %s", importOut) log.Error("Command failed, err: %s", err) os.Exit(1) } } // This is new behavior in Sierra, [openradar](https://openradar.appspot.com/28524119) // You need to use "security set-key-partition-list -S apple-tool:,apple: -k keychainPass keychainName" after importing the item and before attempting to use it via codesign. osVersionCmd := cmdex.NewCommand("sw_vers", "-productVersion") out, err := osVersionCmd.RunAndReturnTrimmedCombinedOutput() if err != nil { log.Error("Failed to get os version, error: %s", err) os.Exit(1) } osVersion, err := version.NewVersion(out) if err != nil { log.Error("Failed to parse os version (%s), error: %s", out, err) os.Exit(1) } sierraVersionStr := "10.12.0" sierraVersion, err := version.NewVersion(sierraVersionStr) if err != nil { log.Error("Failed to parse os version (%s), error: %s", sierraVersionStr, err) os.Exit(1) } if !osVersion.LessThan(sierraVersion) { cmd := cmdex.NewCommand("security", "set-key-partition-list", "-S", "apple-tool:,apple:", "-k", configs.KeychainPassword, configs.KeychainPath) if err := cmd.Run(); err != nil { log.Error("Failed, err: %s", err) os.Exit(1) } } // --- // Set keychain settings: Lock keychain when the system sleeps, Lock keychain after timeout interval, Timeout in seconds settingsOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "-v", "set-keychain-settings", "-lut", "72000", configs.KeychainPath) if err != nil { log.Error("Command failed, output: %s", settingsOut) log.Error("Command failed, err: %s", err) os.Exit(1) } // List keychains listKeychainsOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "list-keychains") if err != nil { log.Error("Command failed, output: %s", listKeychainsOut) log.Error("Command failed, err: %s", err) os.Exit(1) } keychainList := strings.Split(listKeychainsOut, "\n") strippedKeychainList := []string{} for _, keychain := range keychainList { strippedKeychain := strip(keychain) strippedKeychainList = append(strippedKeychainList, strippedKeychain) } strippedKeychainList = addKeyChainToList(strippedKeychainList, configs.KeychainPath) // Set keychain search path args := []string{"-v", "list-keychains", "-s"} args = append(args, strippedKeychainList...) listKeychainsOut, err = runCommandAndReturnCombinedStdoutAndStderr("security", args...) if err != nil { log.Error("Command failed, output: %s", listKeychainsOut) log.Error("Command failed, err: %s", err) os.Exit(1) } // Set the default keychain defaultKeychainOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "-v", "default-keychain", "-s", configs.KeychainPath) if err != nil { log.Error("Command failed, output: %s", defaultKeychainOut) log.Error("Command failed, err: %s", err) os.Exit(1) } // Unlock the specified keychain unlockOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "-v", "unlock-keychain", "-p", configs.KeychainPassword, configs.KeychainPath) if err != nil { log.Error("Command failed, output: %s", unlockOut) log.Error("Command failed, err: %s", err) os.Exit(1) } for cert, pass := range certificatePassphraseMap { certificateIdentity, err := certificateFriendlyName(cert, pass) if err != nil { log.Error("Failed to get cert identity, output: %s", certificateIdentity) log.Error("Failed to get cert identity, err: %s", err) os.Exit(1) } if certificateIdentity == "" { log.Error("Failed to get cert identity") os.Exit(1) } log.Done(" Installed certificate: %s", certificateIdentity) } certs, err := availableCertificates(configs.KeychainPath) if err != nil { log.Error("Failed to get certificate list, err:%s", err) os.Exit(1) } if len(certs) == 0 { log.Error("Failed to import certificate, no certificates found") os.Exit(1) } fmt.Println() log.Info("Available certificates:") fmt.Println("-----------------------") for _, cert := range certs { log.Detail(" * %s", cert) } // // Install provisioning profiles // NOTE: the URL can be a pipe (|) separated list of Provisioning Profile URLs fmt.Println() log.Info("Downloading & installing Provisioning Profile(s)") for idx, profileURL := range provisioningProfileURLs { fmt.Println() log.Detail("=> Downloading provisioning profile: %d/%d", idx+1, profileCount) provisioningProfileExt := "provisionprofile" if !strings.Contains(profileURL, "."+provisioningProfileExt) { provisioningProfileExt = "mobileprovision" } profileTmpPth := path.Join(tempDir, fmt.Sprintf("profile-%d.%s", idx, provisioningProfileExt)) if err := downloadFile(profileTmpPth, profileURL); err != nil { log.Error("Download failed, err: %s", err) os.Exit(1) } fmt.Println() fmt.Println("=> Installing provisioning profile") out, err := runCommandAndReturnCombinedStdoutAndStderr("/usr/bin/security", "cms", "-D", "-i", profileTmpPth) if err != nil { log.Error("Command failed, output: %s", out) log.Error("Command failed, err: %s", err) os.Exit(1) } outSplit := strings.Split(out, "\n") if len(outSplit) > 0 { if strings.Contains(outSplit[0], notValidParameterErrorMessage) { fixedOutSplit := outSplit[1:len(outSplit)] out = strings.Join(fixedOutSplit, "\n") } } tmpProvProfilePth := path.Join(tempDir, "prov") if err := writeBytesToFileWithPermission(tmpProvProfilePth, []byte(out), 0); err != nil { log.Error("Failed to write profile to file, error: %s", err) os.Exit(1) } profileInfos, err := printableProfileInfos(out) if err != nil { log.Error("Failed to read profile infos, err: %s", err) os.Exit(1) } fmt.Println() log.Info("Profile Infos:") log.Detail("%s", profileInfos) fmt.Println() profileUUID, err := runCommandAndReturnCombinedStdoutAndStderr("/usr/libexec/PlistBuddy", "-c", "Print UUID", tmpProvProfilePth) if err != nil { log.Error("Command failed, output: %s", profileUUID) log.Error("Command failed, err: %s", err) os.Exit(1) } log.Done(" Installed Profile UUID: %s", profileUUID) profileFinalPth := path.Join(provisioningProfileDir, profileUUID+"."+provisioningProfileExt) log.Detail(" Moving it to: %s", profileFinalPth) if out, err := runCommandAndReturnCombinedStdoutAndStderr("cp", profileTmpPth, profileFinalPth); err != nil { log.Error("Command failed, output: %s", out) log.Error("Command failed, err: %s", err) os.Exit(1) } } }
func downloadFile(destionationPath, URL string) error { url, err := url.Parse(URL) if err != nil { return err } scheme := url.Scheme tmpDstFilePath := "" if scheme != "file" { log.Detail(" Downloading (%s) to (%s)", secureInput(URL), destionationPath) tmpDir, err := pathutil.NormalizedOSTempDirPath("download") if err != nil { return err } tmpDst := path.Join(tmpDir, "tmp_file") tmpDstFile, err := os.Create(tmpDst) if err != nil { return err } defer func() { if err := tmpDstFile.Close(); err != nil { log.Error("Failed to close file (%s), error: %s", tmpDst, err) } }() success := false var response *http.Response for i := 0; i < 3 && !success; i++ { if i > 0 { fmt.Println("-> Retrying...") time.Sleep(3 * time.Second) } response, err = http.Get(URL) if err != nil { log.Error(err.Error()) } else { success = true } if response != nil { defer func() { if err := response.Body.Close(); err != nil { log.Error("Failed to close response body, error: %s", err) } }() } } if !success { return err } _, err = io.Copy(tmpDstFile, response.Body) if err != nil { return err } tmpDstFilePath = tmpDstFile.Name() } else { log.Detail(" Moving (%s) to (%s)", secureInput(URL), destionationPath) tmpDstFilePath = strings.Replace(URL, scheme+"://", "", -1) } if out, err := runCommandAndReturnCombinedStdoutAndStderr("cp", tmpDstFilePath, destionationPath); err != nil { log.Detail("Move out: %s", out) return err } return nil }
func runTest(buildTestParams models.XcodeBuildTestParamsModel, outputTool, xcprettyOptions string, isAutomaticRetryOnReason, isRetryOnFail bool) (string, int, error) { handleTestError := func(fullOutputStr string, exitCode int, testError error) (string, int, error) { // // Automatic retry for _, retryReasonPattern := range automaticRetryReasonPatterns { if isStringFoundInOutput(retryReasonPattern, fullOutputStr) { log.Warn("Automatic retry reason found in log: %s", retryReasonPattern) if isAutomaticRetryOnReason { log.Detail("isAutomaticRetryOnReason=true - retrying...") return runTest(buildTestParams, outputTool, xcprettyOptions, false, false) } log.Error("isAutomaticRetryOnReason=false, no more retry, stopping the test!") return fullOutputStr, exitCode, testError } } // // Retry on fail if isRetryOnFail { log.Warn("Test run failed") log.Detail("isRetryOnFail=true - retrying...") return runTest(buildTestParams, outputTool, xcprettyOptions, false, false) } return fullOutputStr, exitCode, testError } buildParams := buildTestParams.BuildParams xcodebuildArgs := []string{buildParams.Action, buildParams.ProjectPath, "-scheme", buildParams.Scheme} if buildTestParams.CleanBuild { xcodebuildArgs = append(xcodebuildArgs, "clean") } // the 'build' argument is required *before* the 'test' arg, to prevent // the Xcode bug described in the README, which causes: // 'iPhoneSimulator: Timed out waiting 120 seconds for simulator to boot, current state is 1.' // in case the compilation takes a long time. // Related Radar link: https://openradar.appspot.com/22413115 // Demonstration project: https://github.com/bitrise-io/simulator-launch-timeout-includes-build-time // for builds < 120 seconds or fixed Xcode versions, one should // have the possibility of opting out, because the explicit build arg // leads the project to be compiled twice and increase the duration // Related issue link: https://github.com/bitrise-io/steps-xcode-test/issues/55 if buildTestParams.BuildBeforeTest { xcodebuildArgs = append(xcodebuildArgs, "build") } xcodebuildArgs = append(xcodebuildArgs, "test", "-destination", buildParams.DeviceDestination) if buildTestParams.GenerateCodeCoverage { xcodebuildArgs = append(xcodebuildArgs, "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES") xcodebuildArgs = append(xcodebuildArgs, "GCC_GENERATE_TEST_COVERAGE_FILES=YES") } if buildTestParams.AdditionalOptions != "" { options, err := shellquote.Split(buildTestParams.AdditionalOptions) if err != nil { return "", 1, fmt.Errorf("failed to parse additional options (%s), error: %s", buildTestParams.AdditionalOptions, err) } xcodebuildArgs = append(xcodebuildArgs, options...) } xcprettyArgs := []string{} if xcprettyOptions != "" { options, err := shellquote.Split(xcprettyOptions) if err != nil { return "", 1, fmt.Errorf("failed to parse additional options (%s), error: %s", xcprettyOptions, err) } // get and delete the xcpretty output file, if exists xcprettyOutputFilePath := "" isNextOptOutputPth := false for _, aOpt := range options { if isNextOptOutputPth { xcprettyOutputFilePath = aOpt break } if aOpt == "--output" { isNextOptOutputPth = true continue } } if xcprettyOutputFilePath != "" { if isExist, err := pathutil.IsPathExists(xcprettyOutputFilePath); err != nil { log.Error("Failed to check xcpretty output file status (path: %s), error: %s", xcprettyOutputFilePath, err) } else if isExist { log.Warn("=> Deleting existing xcpretty output: %s", xcprettyOutputFilePath) if err := os.Remove(xcprettyOutputFilePath); err != nil { log.Error("Failed to delete xcpretty output file (path: %s), error: %s", xcprettyOutputFilePath, err) } } } // xcprettyArgs = append(xcprettyArgs, options...) } log.Info("Running the tests...") var rawOutput string var err error var exit int if outputTool == "xcpretty" { rawOutput, exit, err = runPrettyXcodeBuildCmd(true, xcprettyArgs, xcodebuildArgs) } else { rawOutput, exit, err = runXcodeBuildCmd(true, xcodebuildArgs...) } if err != nil { return handleTestError(rawOutput, exit, err) } return rawOutput, exit, nil }
func (configs ConfigsModel) print() { log.Info("ipa export configs:") useCustomExportOptions := (configs.CustomExportOptionsPlistContent != "") if useCustomExportOptions { fmt.Println() log.Warn("Ignoring the following options because CustomExportOptionsPlistContent provided:") } log.Detail("- ExportMethod: %s", configs.ExportMethod) log.Detail("- UploadBitcode: %s", configs.UploadBitcode) log.Detail("- CompileBitcode: %s", configs.CompileBitcode) log.Detail("- TeamID: %s", configs.TeamID) if useCustomExportOptions { log.Warn("----------") } log.Detail("- UseDeprecatedExport: %s", configs.UseDeprecatedExport) log.Detail("- ForceTeamID: %s", configs.ForceTeamID) log.Detail("- ForceProvisioningProfileSpecifier: %s", configs.ForceProvisioningProfileSpecifier) log.Detail("- ForceProvisioningProfile: %s", configs.ForceProvisioningProfile) log.Detail("- ForceCodeSignIdentity: %s", configs.ForceCodeSignIdentity) log.Detail("- CustomExportOptionsPlistContent:") if configs.CustomExportOptionsPlistContent != "" { log.Detail(configs.CustomExportOptionsPlistContent) } fmt.Println() log.Info("xcodebuild configs:") log.Detail("- OutputTool: %s", configs.OutputTool) log.Detail("- Workdir: %s", configs.Workdir) log.Detail("- ProjectPath: %s", configs.ProjectPath) log.Detail("- Scheme: %s", configs.Scheme) log.Detail("- Configuration: %s", configs.Configuration) log.Detail("- OutputDir: %s", configs.OutputDir) log.Detail("- IsCleanBuild: %s", configs.IsCleanBuild) log.Detail("- XcodebuildOptions: %s", configs.XcodebuildOptions) fmt.Println() log.Info("step output configs:") log.Detail("- IsExportXcarchiveZip: %s", configs.IsExportXcarchiveZip) log.Detail("- ExportAllDsyms: %s", configs.ExportAllDsyms) log.Detail("- ArtifactName: %s", configs.ArtifactName) fmt.Println() }
func main() { configs := createConfigsModelFromEnvs() configs.print() if err := configs.validate(); err != nil { log.Error("Issue with input: %s", err) os.Exit(1) } fmt.Println() log.Info("Other Configs:") cleanBuild := (configs.IsCleanBuild == "yes") generateCodeCoverage := (configs.GenerateCodeCoverageFiles == "yes") exportUITestArtifacts := (configs.ExportUITestArtifacts == "true") singleBuild := (configs.IsSingleBuild == "true") buildBeforeTest := (configs.ShouldBuildBeforeTest == "yes") retryOnFail := (configs.ShouldRetryTestOnFail == "yes") // Project-or-Workspace flag action := "" if strings.HasSuffix(configs.ProjectPath, ".xcodeproj") { action = "-project" } else if strings.HasSuffix(configs.ProjectPath, ".xcworkspace") { action = "-workspace" } else { log.Error("Iinvalid project file (%s), extension should be (.xcodeproj/.xcworkspace)", configs.ProjectPath) if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err) } os.Exit(1) } log.Detail("* action: %s", action) // Device Destination deviceDestination := fmt.Sprintf("platform=%s,name=%s,OS=%s", configs.SimulatorPlatform, configs.SimulatorDevice, configs.SimulatorOsVersion) log.Detail("* device_destination: %s", deviceDestination) // Output tools versions xcodebuildVersion, err := xcodeutil.GetXcodeVersion() if err != nil { log.Error("Failed to get the version of xcodebuild! Error: %s", err) if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err) } os.Exit(1) } log.Detail("* xcodebuild_version: %s (%s)", xcodebuildVersion.Version, xcodebuildVersion.BuildVersion) xcprettyVersion, err := cmd.GetXcprettyVersion() if err != nil { log.Warn("Failed to get the xcpretty version! Error: %s", err) } else { log.Detail("* xcpretty_version: %s", xcprettyVersion) } // Simulator infos simulator, err := xcodeutil.GetSimulator(configs.SimulatorPlatform, configs.SimulatorDevice, configs.SimulatorOsVersion) if err != nil { log.Error(fmt.Sprintf("failed to get simulator udid, error: %s", err)) if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err) } os.Exit(1) } log.Detail("* simulator_name: %s, UDID: %s, status: %s", simulator.Name, simulator.SimID, simulator.Status) fmt.Println() buildParams := models.XcodeBuildParamsModel{ Action: action, ProjectPath: configs.ProjectPath, Scheme: configs.Scheme, DeviceDestination: deviceDestination, CleanBuild: cleanBuild, } buildTestParams := models.XcodeBuildTestParamsModel{ BuildParams: buildParams, BuildBeforeTest: buildBeforeTest, AdditionalOptions: configs.TestOptions, GenerateCodeCoverage: generateCodeCoverage, } if singleBuild { buildTestParams.CleanBuild = cleanBuild } // // Start simulator if simulator.Status == "Shutdown" { log.Info("Booting simulator (%s)...", simulator.SimID) if err := xcodeutil.BootSimulator(simulator, xcodebuildVersion); err != nil { log.Error(fmt.Sprintf("failed to boot simulator, error: %s", err)) if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err) } os.Exit(1) } if configs.WaitForSimulatorBoot == "yes" { log.Detail("Waiting for simulator boot") progress.SimpleProgress(".", 1*time.Second, func() { time.Sleep(60 * time.Second) }) } fmt.Println() } // // Run build if !singleBuild { if rawXcodebuildOutput, exitCode, buildErr := runBuild(buildParams, configs.OutputTool); buildErr != nil { if err := saveRawOutputToLogFile(rawXcodebuildOutput, false); err != nil { log.Warn("Failed to save the Raw Output, err: %s", err) } log.Warn("xcode build exit code: %d", exitCode) log.Warn("xcode build log:\n%s", rawXcodebuildOutput) log.Error("xcode build failed with error: %s", buildErr) if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err) } os.Exit(1) } } // // Run test rawXcodebuildOutput, exitCode, testErr := runTest(buildTestParams, configs.OutputTool, configs.XcprettyTestOptions, true, retryOnFail) if err := saveRawOutputToLogFile(rawXcodebuildOutput, (testErr == nil)); err != nil { log.Warn("Failed to save the Raw Output, error %s", err) } if exportUITestArtifacts { if err := saveAttachements(configs.ProjectPath, configs.Scheme); err != nil { log.Warn("Failed to export UI test artifacts, error %s", err) } } if testErr != nil { log.Warn("xcode test exit code: %d", exitCode) log.Error("xcode test failed, error: %s", testErr) hint := `If you can't find the reason of the error in the log, please check the raw-xcodebuild-output.log The log file is stored in $BITRISE_DEPLOY_DIR, and its full path is available in the $BITRISE_XCODE_RAW_TEST_RESULT_TEXT_PATH environment variable` log.Warn(hint) if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err) } os.Exit(1) } if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "succeeded"); err != nil { log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err) } }
func (configs ConfigsModel) print() { log.Info("Configs:") log.Detail("- ApkPath: %s", configs.ApkPath) log.Detail("- CalabashAndroidVersion: %s", configs.CalabashAndroidVersion) log.Detail("- GemFilePath: %s", configs.GemFilePath) }
func main() { configs := createConfigsModelFromEnvs() fmt.Println() configs.print() if err := configs.validate(); err != nil { log.Error("Issue with input: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } // // build fmt.Println() log.Info("Building all iOS Xamarin UITest and Referred Projects in solution: %s", configs.XamarinSolution) builder, err := builder.New(configs.XamarinSolution, []constants.ProjectType{constants.ProjectTypeIOS}, false) if err != nil { log.Error("Failed to create xamarin builder, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } callback := func(solutionName string, projectName string, projectType constants.ProjectType, commandStr string, alreadyPerformed bool) { fmt.Println() if projectType == constants.ProjectTypeXamarinUITest { log.Info("Building test project: %s", projectName) } else { log.Info("Building project: %s", projectName) } log.Done("$ %s", commandStr) if alreadyPerformed { log.Warn("build command already performed, skipping...") } fmt.Println() } warnings, err := builder.BuildAllXamarinUITestAndReferredProjects(configs.XamarinConfiguration, configs.XamarinPlatform, nil, callback) for _, warning := range warnings { log.Warn(warning) } if err != nil { log.Error("Build failed, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } projectOutputMap, err := builder.CollectProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform) if err != nil { log.Error("Failed to collect project outputs, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } testProjectOutputMap, warnings, err := builder.CollectXamarinUITestProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform) for _, warning := range warnings { log.Warn(warning) } if err != nil { log.Error("Failed to collect test project output, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } // --- // // Test Cloud submit solutionDir := filepath.Dir(configs.XamarinSolution) pattern := filepath.Join(solutionDir, "packages/Xamarin.UITest.*/tools/test-cloud.exe") testClouds, err := filepath.Glob(pattern) if err != nil { log.Error("Failed to find test-cloud.exe path with pattern (%s), error: %s", pattern, err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } if len(testClouds) == 0 { if err != nil { log.Error("No test-cloud.exe found path with pattern (%s)", pattern) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } } testCloud, err := testcloud.NewModel(testClouds[0]) if err != nil { log.Error("Failed to create test cloud model, error: %s", err) os.Exit(1) } testCloud.SetAPIKey(configs.APIKey) testCloud.SetUser(configs.User) testCloud.SetDevices(configs.Devices) testCloud.SetIsAsyncJSON(configs.IsAsync == "yes") testCloud.SetSeries(configs.Series) // If test cloud runs in asnyc mode test result will not be saved into file resultLogPth := filepath.Join(configs.DeployDir, "TestResult.xml") if configs.IsAsync != "yes" { testCloud.SetNunitXMLPth(resultLogPth) } // Parallelization if configs.Parallelization != "none" { parallelization, err := testcloud.ParseParallelization(configs.Parallelization) if err != nil { log.Error("Failed to parse parallelization, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } testCloud.SetParallelization(parallelization) } // --- // Custom Options if configs.CustomOptions != "" { options, err := shellquote.Split(configs.CustomOptions) if err != nil { log.Error("Failed to split params (%s), error: %s", configs.CustomOptions, err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } os.Exit(1) } testCloud.SetCustomOptions(options...) } // --- // Artifacts resultLog := "" for testProjectName, testProjectOutput := range testProjectOutputMap { if len(testProjectOutput.ReferredProjectNames) == 0 { log.Warn("Test project (%s) does not refers to any project, skipping...", testProjectName) continue } for _, projectName := range testProjectOutput.ReferredProjectNames { projectOutput, ok := projectOutputMap[projectName] if !ok { continue } ipaPth := "" dsymPth := "" for _, output := range projectOutput.Outputs { if output.OutputType == constants.OutputTypeIPA { ipaPth = output.Pth } if output.OutputType == constants.OutputTypeDSYM { dsymPth = output.Pth } } if ipaPth == "" { log.Warn("No ipa generated for project: %s", projectName) } if dsymPth == "" { log.Warn("No dsym generated for project: %s", projectName) } // Submit fmt.Println() log.Info("Testing (%s) against (%s)", testProjectName, projectName) log.Detail("test dll: %s", testProjectOutput.Output.Pth) log.Detail("ipa: %s", ipaPth) log.Detail("dsym: %s", dsymPth) testCloud.SetAssemblyDir(filepath.Dir(testProjectOutput.Output.Pth)) testCloud.SetIPAPth(ipaPth) testCloud.SetDSYMPth(dsymPth) fmt.Println() log.Info("Submitting:") log.Done("$ %s", testCloud.PrintableCommand()) lines := []string{} callback := func(line string) { log.Detail(line) lines = append(lines, line) } err := testCloud.Submit(callback) // If test cloud runs in asnyc mode test result will not be saved into file if configs.IsAsync != "yes" { testLog, logErr := testResultLogContent(resultLogPth) if logErr != nil { log.Warn("Failed to read test result, error: %s", logErr) } resultLog = testLog } if err != nil { log.Error("Submit failed, error: %s", err) if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } if resultLog != "" { if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err) } } os.Exit(1) } // --- if configs.IsAsync == "yes" { fmt.Println() log.Info("Preocessing json result:") jsonLine := "" for _, line := range lines { if strings.HasPrefix(line, "{") && strings.HasSuffix(line, "}") { jsonLine = line } } if jsonLine != "" { var result JSONResultModel if err := json.Unmarshal([]byte(jsonLine), &result); err != nil { log.Error("Failed to unmarshal result, error: %s", err) } else { for _, errorMsg := range result.ErrorMessages { log.Error(errorMsg) } if len(result.ErrorMessages) > 0 { if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } if resultLog != "" { if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err) } } os.Exit(1) } if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_TO_RUN_ID", result.TestRunID); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_TO_RUN_ID", err) } log.Done("TestRunId (%s) is available in (%s) environment variable", result.TestRunID, "BITRISE_XAMARIN_TEST_TO_RUN_ID") } } } } } // --- if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "succeeded"); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err) } if resultLog != "" { if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil { log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err) } } }
func (configs ConfigsModel) print() { log.Info("Configs:") log.Detail("- XamarinSolution: %s", configs.XamarinSolution) log.Detail("- NugetVersion: %s", configs.NugetVersion) }
func main() { configs := createConfigsModelFromEnvs() fmt.Println() configs.print() if err := configs.validate(); err != nil { log.Error("Issue with input: %s", err) os.Exit(1) } nugetPth := "/Library/Frameworks/Mono.framework/Versions/Current/bin/nuget" nugetRestoreCmdArgs := []string{nugetPth} if configs.NugetVersion == "latest" { fmt.Println() log.Info("Updating Nuget to latest version...") // "sudo $nuget update -self" cmdArgs := []string{"sudo", nugetPth, "update", "-self"} cmd, err := cmdex.NewCommandFromSlice(cmdArgs) if err != nil { log.Error("Failed to create command from args (%v), error: %s", cmdArgs, err) os.Exit(1) } cmd.SetStdout(os.Stdout) cmd.SetStderr(os.Stderr) log.Done("$ %s", cmdex.PrintableCommandArgs(false, cmdArgs)) if err := cmd.Run(); err != nil { log.Error("Failed to update nuget, error: %s", err) os.Exit(1) } } else if configs.NugetVersion != "" { fmt.Println() log.Info("Downloading Nuget %s version...", configs.NugetVersion) tmpDir, err := pathutil.NormalizedOSTempDirPath("__nuget__") if err != nil { log.Error("Failed to create tmp dir, error: %s", err) os.Exit(1) } downloadPth := filepath.Join(tmpDir, "nuget.exe") // https://dist.nuget.org/win-x86-commandline/v3.3.0/nuget.exe nugetURL := fmt.Sprintf("https://dist.nuget.org/win-x86-commandline/v%s/nuget.exe", configs.NugetVersion) log.Detail("Download URL: %s", nugetURL) if err := DownloadFile(nugetURL, downloadPth); err != nil { log.Warn("Download failed, error: %s", err) // https://dist.nuget.org/win-x86-commandline/v3.4.4/NuGet.exe nugetURL = fmt.Sprintf("https://dist.nuget.org/win-x86-commandline/v%s/NuGet.exe", configs.NugetVersion) log.Detail("Retry download URl: %s", nugetURL) if err := DownloadFile(nugetURL, downloadPth); err != nil { log.Error("Failed to download nuget, error: %s", err) os.Exit(1) } } nugetRestoreCmdArgs = []string{constants.MonoPath, downloadPth} } fmt.Println() log.Info("Restoring Nuget packages...") nugetRestoreCmdArgs = append(nugetRestoreCmdArgs, "restore", configs.XamarinSolution) if err := retry.Times(1).Try(func(attempt uint) error { if attempt > 0 { log.Warn("Attempt %d failed, retrying...", attempt) } log.Done("$ %s", cmdex.PrintableCommandArgs(false, nugetRestoreCmdArgs)) cmd, err := cmdex.NewCommandFromSlice(nugetRestoreCmdArgs) if err != nil { log.Error("Failed to create Nuget command, error: %s", err) os.Exit(1) } cmd.SetStdout(os.Stdout) cmd.SetStderr(os.Stderr) if err := cmd.Run(); err != nil { log.Error("Restore failed, error: %s", err) return err } return nil }); err != nil { log.Error("Nuget restore failed, error: %s", err) os.Exit(1) } }
func main() { configs := createConfigsModelFromEnvs() fmt.Println() configs.print() if err := configs.validate(); err != nil { fail("Issue with input: %s", err) } log.Info("step determined configs:") // Detect Xcode major version xcodebuildVersion, err := utils.XcodeBuildVersion() if err != nil { fail("Failed to determin xcode version, error: %s", err) } log.Detail("- xcodebuildVersion: %s (%s)", xcodebuildVersion.XcodeVersion.String(), xcodebuildVersion.BuildVersion) xcodeMajorVersion := xcodebuildVersion.XcodeVersion.Segments()[0] if xcodeMajorVersion < minSupportedXcodeMajorVersion { fail("Invalid xcode major version (%s), should not be less then min supported: %d", xcodeMajorVersion, minSupportedXcodeMajorVersion) } // Detect xcpretty version if configs.OutputTool == "xcpretty" { if !utils.IsXcprettyInstalled() { fail(`xcpretty is not installed For xcpretty installation see: 'https://github.com/supermarin/xcpretty', or use 'xcodebuild' as 'output_tool'.`) } xcprettyVersion, err := utils.XcprettyVersion() if err != nil { fail("Failed to determin xcpretty version, error: %s", err) } log.Detail("- xcprettyVersion: %s", xcprettyVersion.String()) } // Validation CustomExportOptionsPlistContent if configs.CustomExportOptionsPlistContent != "" && xcodeMajorVersion < 7 { log.Warn("CustomExportOptionsPlistContent is set, but CustomExportOptionsPlistContent only used if xcodeMajorVersion > 6") configs.CustomExportOptionsPlistContent = "" } if configs.ForceProvisioningProfileSpecifier != "" && xcodeMajorVersion < 8 { log.Warn("ForceProvisioningProfileSpecifier is set, but ForceProvisioningProfileSpecifier only used if xcodeMajorVersion > 7") configs.ForceProvisioningProfileSpecifier = "" } if configs.ForceTeamID == "" && xcodeMajorVersion < 8 { log.Warn("ForceTeamID is set, but ForceTeamID only used if xcodeMajorVersion > 7") configs.ForceTeamID = "" } if configs.ForceProvisioningProfileSpecifier != "" && configs.ForceProvisioningProfile != "" { log.Warn("both ForceProvisioningProfileSpecifier and ForceProvisioningProfile are set, using ForceProvisioningProfileSpecifier") configs.ForceProvisioningProfile = "" } fmt.Println() // abs out dir pth absOutputDir, err := pathutil.AbsPath(configs.OutputDir) if err != nil { fail("Failed to expand OutputDir (%s), error: %s", configs.OutputDir, err) } configs.OutputDir = absOutputDir if exist, err := pathutil.IsPathExists(configs.OutputDir); err != nil { fail("Failed to check if OutputDir exist, error: %s", err) } else if !exist { if err := os.MkdirAll(configs.OutputDir, 0777); err != nil { fail("Failed to create OutputDir (%s), error: %s", configs.OutputDir, err) } } // output files tmpArchiveDir, err := pathutil.NormalizedOSTempDirPath("__archive__") if err != nil { fail("Failed to create temp dir for archives, error: %s", err) } tmpArchivePath := filepath.Join(tmpArchiveDir, configs.ArtifactName+".xcarchive") appPath := filepath.Join(configs.OutputDir, configs.ArtifactName+".app") ipaPath := filepath.Join(configs.OutputDir, configs.ArtifactName+".ipa") exportOptionsPath := filepath.Join(configs.OutputDir, "export_options.plist") rawXcodebuildOutputLogPath := filepath.Join(configs.OutputDir, "raw-xcodebuild-output.log") dsymZipPath := filepath.Join(configs.OutputDir, configs.ArtifactName+".dSYM.zip") archiveZipPath := filepath.Join(configs.OutputDir, configs.ArtifactName+".xcarchive.zip") ideDistributionLogsZipPath := filepath.Join(configs.OutputDir, "xcodebuild.xcdistributionlogs.zip") // cleanup filesToCleanup := []string{ appPath, ipaPath, exportOptionsPath, rawXcodebuildOutputLogPath, dsymZipPath, archiveZipPath, ideDistributionLogsZipPath, } for _, pth := range filesToCleanup { if exist, err := pathutil.IsPathExists(pth); err != nil { fail("Failed to check if path (%s) exist, error: %s", pth, err) } else if exist { if err := os.RemoveAll(pth); err != nil { fail("Failed to remove path (%s), error: %s", pth, err) } } } // // Create the Archive with Xcode Command Line tools log.Info("Create the Archive ...") fmt.Println() isWorkspace := false ext := filepath.Ext(configs.ProjectPath) if ext == ".xcodeproj" { isWorkspace = false } else if ext == ".xcworkspace" { isWorkspace = true } else { fail("Project file extension should be .xcodeproj or .xcworkspace, but got: %s", ext) } archiveCmd := xcodebuild.NewArchiveCommand(configs.ProjectPath, isWorkspace) archiveCmd.SetScheme(configs.Scheme) archiveCmd.SetConfiguration(configs.Configuration) if configs.ForceTeamID != "" { log.Detail("Forcing Development Team: %s", configs.ForceTeamID) archiveCmd.SetForceDevelopmentTeam(configs.ForceTeamID) } if configs.ForceProvisioningProfileSpecifier != "" { log.Detail("Forcing Provisioning Profile Specifier: %s", configs.ForceProvisioningProfileSpecifier) archiveCmd.SetForceProvisioningProfileSpecifier(configs.ForceProvisioningProfileSpecifier) } if configs.ForceProvisioningProfile != "" { log.Detail("Forcing Provisioning Profile: %s", configs.ForceProvisioningProfile) archiveCmd.SetForceProvisioningProfile(configs.ForceProvisioningProfile) } if configs.ForceCodeSignIdentity != "" { log.Detail("Forcing Code Signing Identity: %s", configs.ForceCodeSignIdentity) archiveCmd.SetForceCodeSignIdentity(configs.ForceCodeSignIdentity) } if configs.IsCleanBuild == "yes" { archiveCmd.SetCustomBuildAction("clean") } archiveCmd.SetArchivePath(tmpArchivePath) if configs.XcodebuildOptions != "" { options, err := shellquote.Split(configs.XcodebuildOptions) if err != nil { fail("Failed to shell split XcodebuildOptions (%s), error: %s", configs.XcodebuildOptions) } archiveCmd.SetCustomOptions(options) } if configs.OutputTool == "xcpretty" { xcprettyCmd := xcpretty.New(archiveCmd) logWithTimestamp(colorstring.Green, "$ %s", xcprettyCmd.PrintableCmd()) fmt.Println() if rawXcodebuildOut, err := xcprettyCmd.Run(); err != nil { if err := utils.ExportOutputFileContent(rawXcodebuildOut, rawXcodebuildOutputLogPath, bitriseXcodeRawResultTextEnvKey); err != nil { log.Warn("Failed to export %s, error: %s", bitriseXcodeRawResultTextEnvKey, err) } else { log.Warn(`If you can't find the reason of the error in the log, please check the raw-xcodebuild-output.log The log file is stored in $BITRISE_DEPLOY_DIR, and its full path is available in the $BITRISE_XCODE_RAW_RESULT_TEXT_PATH environment variable`) } fail("Archive failed, error: %s", err) } } else { logWithTimestamp(colorstring.Green, "$ %s", archiveCmd.PrintableCmd()) fmt.Println() if err := archiveCmd.Run(); err != nil { fail("Archive failed, error: %s", err) } } fmt.Println() // Ensure xcarchive exists if exist, err := pathutil.IsPathExists(tmpArchivePath); err != nil { fail("Failed to check if archive exist, error: %s", err) } else if !exist { fail("No archive generated at: %s", tmpArchivePath) } // // Exporting the ipa with Xcode Command Line tools /* You'll get a "Error Domain=IDEDistributionErrorDomain Code=14 "No applicable devices found."" error if $GEM_HOME is set and the project's directory includes a Gemfile - to fix this we'll unset GEM_HOME as that's not required for xcodebuild anyway. This probably fixes the RVM issue too, but that still should be tested. See also: - http://stackoverflow.com/questions/33041109/xcodebuild-no-applicable-devices-found-when-exporting-archive - https://gist.github.com/claybridges/cea5d4afd24eda268164 */ log.Info("Exporting ipa from the archive...") fmt.Println() envsToUnset := []string{"GEM_HOME", "GEM_PATH", "RUBYLIB", "RUBYOPT", "BUNDLE_BIN_PATH", "_ORIGINAL_GEM_PATH", "BUNDLE_GEMFILE"} for _, key := range envsToUnset { if err := os.Unsetenv(key); err != nil { fail("Failed to unset (%s), error: %s", key, err) } } if xcodeMajorVersion == 6 || configs.UseDeprecatedExport == "yes" { log.Detail("Using legacy export") /* Get the name of the profile which was used for creating the archive --> Search for embedded.mobileprovision in the xcarchive. It should contain a .app folder in the xcarchive folder under the Products/Applications folder */ embeddedProfilePth, err := xcarchive.EmbeddedMobileProvisionPth(tmpArchivePath) if err != nil { fail("Failed to get embedded profile path, error: %s", err) } provProfile, err := provisioningprofile.NewFromFile(embeddedProfilePth) if err != nil { fail("Failed to create provisioning profile model, error: %s", err) } if provProfile.Name == nil { fail("Profile name empty") } legacyExportCmd := xcodebuild.NewLegacyExportCommand() legacyExportCmd.SetExportFormat("ipa") legacyExportCmd.SetArchivePath(tmpArchivePath) legacyExportCmd.SetExportPath(ipaPath) legacyExportCmd.SetExportProvisioningProfileName(*provProfile.Name) if configs.OutputTool == "xcpretty" { xcprettyCmd := xcpretty.New(legacyExportCmd) logWithTimestamp(colorstring.Green, xcprettyCmd.PrintableCmd()) fmt.Println() if rawXcodebuildOut, err := xcprettyCmd.Run(); err != nil { if err := utils.ExportOutputFileContent(rawXcodebuildOut, rawXcodebuildOutputLogPath, bitriseXcodeRawResultTextEnvKey); err != nil { log.Warn("Failed to export %s, error: %s", bitriseXcodeRawResultTextEnvKey, err) } else { log.Warn(`If you can't find the reason of the error in the log, please check the raw-xcodebuild-output.log The log file is stored in $BITRISE_DEPLOY_DIR, and its full path is available in the $BITRISE_XCODE_RAW_RESULT_TEXT_PATH environment variable`) } fail("Export failed, error: %s", err) } } else { logWithTimestamp(colorstring.Green, legacyExportCmd.PrintableCmd()) fmt.Println() if err := legacyExportCmd.Run(); err != nil { fail("Export failed, error: %s", err) } } } else { log.Detail("Using export options") if configs.CustomExportOptionsPlistContent != "" { log.Detail("Custom export options content provided:") fmt.Println(configs.CustomExportOptionsPlistContent) if err := fileutil.WriteStringToFile(exportOptionsPath, configs.CustomExportOptionsPlistContent); err != nil { fail("Failed to write export options to file, error: %s", err) } } else { log.Detail("Generating export options") var method exportoptions.Method if configs.ExportMethod == "auto-detect" { log.Detail("auto-detect export method, based on embedded profile") embeddedProfilePth, err := xcarchive.EmbeddedMobileProvisionPth(tmpArchivePath) if err != nil { fail("Failed to get embedded profile path, error: %s", err) } provProfile, err := provisioningprofile.NewFromFile(embeddedProfilePth) if err != nil { fail("Failed to create provisioning profile model, error: %s", err) } method = provProfile.GetExportMethod() log.Detail("detected export method: %s", method) } else { log.Detail("using export-method input: %s", configs.ExportMethod) parsedMethod, err := exportoptions.ParseMethod(configs.ExportMethod) if err != nil { fail("Failed to parse export options, error: %s", err) } method = parsedMethod } var exportOpts exportoptions.ExportOptions if method == exportoptions.MethodAppStore { options := exportoptions.NewAppStoreOptions() options.UploadBitcode = (configs.UploadBitcode == "yes") options.TeamID = configs.TeamID exportOpts = options } else { options := exportoptions.NewNonAppStoreOptions(method) options.CompileBitcode = (configs.CompileBitcode == "yes") options.TeamID = configs.TeamID exportOpts = options } log.Detail("generated export options content:") fmt.Println() fmt.Println(exportOpts.String()) if err = exportOpts.WriteToFile(exportOptionsPath); err != nil { fail("Failed to write export options to file, error: %s", err) } } fmt.Println() tmpDir, err := pathutil.NormalizedOSTempDirPath("__export__") if err != nil { fail("Failed to create tmp dir, error: %s", err) } exportCmd := xcodebuild.NewExportCommand() exportCmd.SetArchivePath(tmpArchivePath) exportCmd.SetExportDir(tmpDir) exportCmd.SetExportOptionsPlist(exportOptionsPath) if configs.OutputTool == "xcpretty" { xcprettyCmd := xcpretty.New(exportCmd) logWithTimestamp(colorstring.Green, xcprettyCmd.PrintableCmd()) fmt.Println() if xcodebuildOut, err := xcprettyCmd.Run(); err != nil { // xcodebuild raw output if err := utils.ExportOutputFileContent(xcodebuildOut, rawXcodebuildOutputLogPath, bitriseXcodeRawResultTextEnvKey); err != nil { log.Warn("Failed to export %s, error: %s", bitriseXcodeRawResultTextEnvKey, err) } else { log.Warn(`If you can't find the reason of the error in the log, please check the raw-xcodebuild-output.log The log file is stored in $BITRISE_DEPLOY_DIR, and its full path is available in the $BITRISE_XCODE_RAW_RESULT_TEXT_PATH environment variable`) } // xcdistributionlogs if logsDirPth, err := findIDEDistrubutionLogsPath(xcodebuildOut); err != nil { log.Warn("Failed to find xcdistributionlogs, error: %s", err) } else if err := utils.ExportOutputDirAsZip(logsDirPth, ideDistributionLogsZipPath, bitriseIDEDistributionLogsPthEnvKey); err != nil { log.Warn("Failed to export %s, error: %s", bitriseIDEDistributionLogsPthEnvKey, err) } else { log.Warn(`Also please check the xcdistributionlogs The logs directory is stored in $BITRISE_DEPLOY_DIR, and its full path is available in the $BITRISE_IDEDISTRIBUTION_LOGS_PATH environment variable`) } fail("Export failed, error: %s", err) } } else { logWithTimestamp(colorstring.Green, exportCmd.PrintableCmd()) fmt.Println() if xcodebuildOut, err := exportCmd.RunAndReturnOutput(); err != nil { // xcdistributionlogs if logsDirPth, err := findIDEDistrubutionLogsPath(xcodebuildOut); err != nil { log.Warn("Failed to find xcdistributionlogs, error: %s", err) } else if err := utils.ExportOutputDirAsZip(logsDirPth, ideDistributionLogsZipPath, bitriseIDEDistributionLogsPthEnvKey); err != nil { log.Warn("Failed to export %s, error: %s", bitriseIDEDistributionLogsPthEnvKey, err) } else { log.Warn(`If you can't find the reason of the error in the log, please check the xcdistributionlogs The logs directory is stored in $BITRISE_DEPLOY_DIR, and its full path is available in the $BITRISE_IDEDISTRIBUTION_LOGS_PATH environment variable`) } fail("Export failed, error: %s", err) } } // Search for ipa pattern := filepath.Join(tmpDir, "*.ipa") ipas, err := filepath.Glob(pattern) if err != nil { fail("Failed to collect ipa files, error: %s", err) } if len(ipas) == 0 { fail("No ipa found with pattern: %s", pattern) } else if len(ipas) == 1 { if err := cmdex.CopyFile(ipas[0], ipaPath); err != nil { fail("Failed to copy (%s) -> (%s), error: %s", ipas[0], ipaPath, err) } } else { log.Warn("More than 1 .ipa file found") for _, ipa := range ipas { base := filepath.Base(ipa) deployPth := filepath.Join(configs.OutputDir, base) if err := cmdex.CopyFile(ipa, deployPth); err != nil { fail("Failed to copy (%s) -> (%s), error: %s", ipas[0], ipaPath, err) } ipaPath = ipa } } } log.Info("Exporting outputs...") // // Export outputs // Export .xcarchive fmt.Println() if err := utils.ExportOutputDir(tmpArchivePath, tmpArchivePath, bitriseXCArchivePthEnvKey); err != nil { fail("Failed to export %s, error: %s", bitriseXCArchivePthEnvKey, err) } log.Done("The xcarchive path is now available in the Environment Variable: %s (value: %s)", bitriseXCArchivePthEnvKey, tmpArchivePath) if configs.IsExportXcarchiveZip == "yes" { if err := utils.ExportOutputDirAsZip(tmpArchivePath, archiveZipPath, bitriseXCArchiveZipPthEnvKey); err != nil { fail("Failed to export %s, error: %s", bitriseXCArchiveZipPthEnvKey, err) } log.Done("The xcarchive zip path is now available in the Environment Variable: %s (value: %s)", bitriseXCArchiveZipPthEnvKey, archiveZipPath) } // Export .app fmt.Println() exportedApp, err := xcarchive.FindApp(tmpArchivePath) if err != nil { fail("Failed to find app, error: %s", err) } if err := utils.ExportOutputDir(exportedApp, exportedApp, bitriseAppDirPthEnvKey); err != nil { fail("Failed to export %s, error: %s", bitriseAppDirPthEnvKey, err) } log.Done("The app directory is now available in the Environment Variable: %s (value: %s)", bitriseAppDirPthEnvKey, appPath) // Export .ipa fmt.Println() if err := utils.ExportOutputFile(ipaPath, ipaPath, bitriseIPAPthEnvKey); err != nil { fail("Failed to export %s, error: %s", bitriseIPAPthEnvKey, err) } log.Done("The ipa path is now available in the Environment Variable: %s (value: %s)", bitriseIPAPthEnvKey, ipaPath) // Export .dSYMs fmt.Println() appDSYM, frameworkDSYMs, err := xcarchive.FindDSYMs(tmpArchivePath) if err != nil { fail("Failed to export dsyms, error: %s", err) } dsymDir, err := pathutil.NormalizedOSTempDirPath("__dsyms__") if err != nil { fail("Failed to create tmp dir, error: %s", err) } if err := cmdex.CopyDir(appDSYM, dsymDir, false); err != nil { fail("Failed to copy (%s) -> (%s), error: %s", appDSYM, dsymDir, err) } if configs.ExportAllDsyms == "yes" { for _, dsym := range frameworkDSYMs { if err := cmdex.CopyDir(dsym, dsymDir, false); err != nil { fail("Failed to copy (%s) -> (%s), error: %s", dsym, dsymDir, err) } } } if err := utils.ExportOutputDir(dsymDir, dsymDir, bitriseDSYMDirPthEnvKey); err != nil { fail("Failed to export %s, error: %s", bitriseDSYMDirPthEnvKey, err) } log.Done("The dSYM dir path is now available in the Environment Variable: %s (value: %s)", bitriseDSYMDirPthEnvKey, dsymDir) if err := utils.ExportOutputDirAsZip(dsymDir, dsymZipPath, bitriseDSYMPthEnvKey); err != nil { fail("Failed to export %s, error: %s", bitriseDSYMPthEnvKey, err) } log.Done("The dSYM zip path is now available in the Environment Variable: %s (value: %s)", bitriseDSYMPthEnvKey, dsymZipPath) }